差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
工具:编辑:herokuv2ray [2023-07-22 07:13] – [CloudFlare 免费翻墙工具] goldentianya | 工具:编辑:herokuv2ray [2024-01-23 07:11] (当前版本) – [X-UI 搭建] goldentianya | ||
---|---|---|---|
行 66: | 行 66: | ||
====== X-UI 搭建 ====== | ====== X-UI 搭建 ====== | ||
[[https:// | [[https:// | ||
+ | |||
+ | [[https:// | ||
x-ui 一键安装 | x-ui 一键安装 | ||
行 115: | 行 117: | ||
[[https:// | [[https:// | ||
+ | |||
+ | 更新分流文件的命令,需要每年执行一次。 | ||
+ | wget https:// | ||
++++ 路由设置 | | ++++ 路由设置 | | ||
行 141: | 行 146: | ||
- 证书放在 <color # | - 证书放在 <color # | ||
====== CloudFlare 免费翻墙工具 ====== | ====== CloudFlare 免费翻墙工具 ====== | ||
- | 每秒的IP都变化。采用 simpinvest.eu.org 域名来实现这个机场。 | ||
- | 进入 CF => Worker and Pages => create Worker | + | [[it: |
- | 打开v2ray 添加vlessf服务器,点击生成一个用户id,并复制到脚本的 userID 中去。 | + | [[https:// |
- | 五个加速地址 | + | [[https://www.kuaidaili.com/free/|免费proxy]] |
- | <code | download> | + | |
- | | + | |
- | edgetunnel.anycast.eu.org | + | |
- | cdn-b100.xn--b6gac.eu.org | + | |
- | | + | |
- | | + | |
- | </ | + | |
- | + | ||
- | + | ||
- | ++++ worker.js | | + | |
- | + | ||
- | <code | download> | + | |
- | // < | + | |
- | // @ts-ignore | + | |
- | import { connect } from ' | + | |
- | + | ||
- | // How to generate your own UUID: | + | |
- | // [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]:: | + | |
- | let userID = ' | + | |
- | + | ||
- | let proxyIP = ''; | + | |
- | + | ||
- | + | ||
- | if (!isValidUUID(userID)) { | + | |
- | throw new Error(' | + | |
- | } | + | |
- | + | ||
- | export default { | + | |
- | /** | + | |
- | * @param {import(" | + | |
- | * @param {{UUID: string, PROXYIP: string}} env | + | |
- | * @param {import(" | + | |
- | * @returns {Promise< | + | |
- | */ | + | |
- | async fetch(request, | + | |
- | try { | + | |
- | userID = env.UUID || userID; | + | |
- | proxyIP = env.PROXYIP || proxyIP; | + | |
- | const upgradeHeader = request.headers.get(' | + | |
- | if (!upgradeHeader || upgradeHeader !== ' | + | |
- | const url = new URL(request.url); | + | |
- | switch (url.pathname) { | + | |
- | case '/': | + | |
- | return new Response(JSON.stringify(request.cf), | + | |
- | case `/ | + | |
- | const vlessConfig = getVLESSConfig(userID, | + | |
- | return new Response(`${vlessConfig}`, | + | |
- | status: | + | |
- | headers: | + | |
- | " | + | |
- | } | + | |
- | }); | + | |
- | } | + | |
- | default: | + | |
- | return new Response(' | + | |
- | } | + | |
- | } else { | + | |
- | return await vlessOverWSHandler(request); | + | |
- | } | + | |
- | } catch (err) { | + | |
- | /** @type {Error} */ let e = err; | + | |
- | return new Response(e.toString()); | + | |
- | } | + | |
- | }, | + | |
- | }; | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {import(" | + | |
- | */ | + | |
- | async function vlessOverWSHandler(request) { | + | |
- | + | ||
- | /** @type {import(" | + | |
- | // @ts-ignore | + | |
- | const webSocketPair = new WebSocketPair(); | + | |
- | const [client, webSocket] = Object.values(webSocketPair); | + | |
- | + | ||
- | webSocket.accept(); | + | |
- | + | ||
- | let address = ''; | + | |
- | let portWithRandomLog = ''; | + | |
- | const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { | + | |
- | console.log(`[${address}: | + | |
- | }; | + | |
- | const earlyDataHeader = request.headers.get(' | + | |
- | + | ||
- | const readableWebSocketStream = makeReadableWebSocketStream(webSocket, | + | |
- | + | ||
- | /** @type {{ value: import(" | + | |
- | let remoteSocketWapper = { | + | |
- | value: null, | + | |
- | }; | + | |
- | let udpStreamWrite = null; | + | |
- | let isDns = false; | + | |
- | + | ||
- | // ws --> remote | + | |
- | readableWebSocketStream.pipeTo(new WritableStream({ | + | |
- | async write(chunk, | + | |
- | if (isDns && udpStreamWrite) { | + | |
- | return udpStreamWrite(chunk); | + | |
- | } | + | |
- | if (remoteSocketWapper.value) { | + | |
- | const writer = remoteSocketWapper.value.writable.getWriter() | + | |
- | await writer.write(chunk); | + | |
- | writer.releaseLock(); | + | |
- | return; | + | |
- | } | + | |
- | + | ||
- | const { | + | |
- | hasError, | + | |
- | message, | + | |
- | portRemote = 443, | + | |
- | addressRemote = '', | + | |
- | rawDataIndex, | + | |
- | vlessVersion = new Uint8Array([0, | + | |
- | isUDP, | + | |
- | } = processVlessHeader(chunk, | + | |
- | address = addressRemote; | + | |
- | portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp ' | + | |
- | } `; | + | |
- | if (hasError) { | + | |
- | // controller.error(message); | + | |
- | throw new Error(message); | + | |
- | // webSocket.close(1000, | + | |
- | return; | + | |
- | } | + | |
- | // if UDP but port not DNS port, close it | + | |
- | if (isUDP) { | + | |
- | if (portRemote === 53) { | + | |
- | isDns = true; | + | |
- | } else { | + | |
- | // controller.error(' | + | |
- | throw new Error(' | + | |
- | return; | + | |
- | } | + | |
- | } | + | |
- | // [" | + | |
- | const vlessResponseHeader = new Uint8Array([vlessVersion[0], | + | |
- | const rawClientData = chunk.slice(rawDataIndex); | + | |
- | + | ||
- | // TODO: support udp here when cf runtime has udp support | + | |
- | if (isDns) { | + | |
- | const { write } = await handleUDPOutBound(webSocket, | + | |
- | udpStreamWrite = write; | + | |
- | udpStreamWrite(rawClientData); | + | |
- | return; | + | |
- | } | + | |
- | handleTCPOutBound(remoteSocketWapper, | + | |
- | }, | + | |
- | close() { | + | |
- | log(`readableWebSocketStream is close`); | + | |
- | }, | + | |
- | abort(reason) { | + | |
- | log(`readableWebSocketStream is abort`, JSON.stringify(reason)); | + | |
- | }, | + | |
- | })).catch((err) => { | + | |
- | log(' | + | |
- | }); | + | |
- | + | ||
- | return new Response(null, | + | |
- | status: 101, | + | |
- | // @ts-ignore | + | |
- | webSocket: | + | |
- | }); | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * Handles outbound TCP connections. | + | |
- | * | + | |
- | * @param {any} remoteSocket | + | |
- | * @param {string} addressRemote The remote address to connect to. | + | |
- | * @param {number} portRemote The remote port to connect to. | + | |
- | * @param {Uint8Array} rawClientData The raw client data to write. | + | |
- | * @param {import(" | + | |
- | * @param {Uint8Array} vlessResponseHeader The VLESS response header. | + | |
- | * @param {function} log The logging function. | + | |
- | * @returns {Promise< | + | |
- | */ | + | |
- | async function handleTCPOutBound(remoteSocket, | + | |
- | async function connectAndWrite(address, | + | |
- | /** @type {import(" | + | |
- | const tcpSocket = connect({ | + | |
- | hostname: | + | |
- | port: port, | + | |
- | }); | + | |
- | remoteSocket.value = tcpSocket; | + | |
- | log(`connected to ${address}: | + | |
- | const writer = tcpSocket.writable.getWriter(); | + | |
- | await writer.write(rawClientData); | + | |
- | writer.releaseLock(); | + | |
- | return tcpSocket; | + | |
- | } | + | |
- | + | ||
- | // if the cf connect tcp socket have no incoming data, we retry to redirect ip | + | |
- | async function retry() { | + | |
- | const tcpSocket = await connectAndWrite(proxyIP || addressRemote, | + | |
- | // no matter retry success or not, close websocket | + | |
- | tcpSocket.closed.catch(error => { | + | |
- | console.log(' | + | |
- | }).finally(() => { | + | |
- | safeCloseWebSocket(webSocket); | + | |
- | }) | + | |
- | remoteSocketToWS(tcpSocket, | + | |
- | } | + | |
- | + | ||
- | const tcpSocket = await connectAndWrite(addressRemote, | + | |
- | + | ||
- | // when remoteSocket is ready, pass to websocket | + | |
- | // remote--> | + | |
- | remoteSocketToWS(tcpSocket, | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {import(" | + | |
- | * @param {string} earlyDataHeader for ws 0rtt | + | |
- | * @param {(info: string)=> | + | |
- | */ | + | |
- | function makeReadableWebSocketStream(webSocketServer, | + | |
- | let readableStreamCancel = false; | + | |
- | const stream = new ReadableStream({ | + | |
- | start(controller) { | + | |
- | webSocketServer.addEventListener(' | + | |
- | if (readableStreamCancel) { | + | |
- | return; | + | |
- | } | + | |
- | const message = event.data; | + | |
- | controller.enqueue(message); | + | |
- | }); | + | |
- | + | ||
- | // The event means that the client closed the client -> server stream. | + | |
- | // However, the server -> client stream is still open until you call close() on the server side. | + | |
- | // The WebSocket protocol says that a separate close message must be sent in each direction to fully close the socket. | + | |
- | webSocketServer.addEventListener(' | + | |
- | // client send close, need close server | + | |
- | // if stream is cancel, skip controller.close | + | |
- | safeCloseWebSocket(webSocketServer); | + | |
- | if (readableStreamCancel) { | + | |
- | return; | + | |
- | } | + | |
- | controller.close(); | + | |
- | } | + | |
- | ); | + | |
- | webSocketServer.addEventListener(' | + | |
- | log(' | + | |
- | controller.error(err); | + | |
- | } | + | |
- | ); | + | |
- | // for ws 0rtt | + | |
- | const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); | + | |
- | if (error) { | + | |
- | controller.error(error); | + | |
- | } else if (earlyData) { | + | |
- | controller.enqueue(earlyData); | + | |
- | } | + | |
- | }, | + | |
- | + | ||
- | pull(controller) { | + | |
- | // if ws can stop read if stream is full, we can implement backpressure | + | |
- | // https://streams.spec.whatwg.org/# | + | |
- | }, | + | |
- | cancel(reason) { | + | |
- | // 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here | + | |
- | // 2. if readableStream is cancel, all controller.close/ | + | |
- | // 3. but from testing controller.error still work even if readableStream is cancel | + | |
- | if (readableStreamCancel) { | + | |
- | return; | + | |
- | } | + | |
- | log(`ReadableStream was canceled, due to ${reason}`) | + | |
- | readableStreamCancel = true; | + | |
- | safeCloseWebSocket(webSocketServer); | + | |
- | } | + | |
- | }); | + | |
- | + | ||
- | return stream; | + | |
- | + | ||
- | } | + | |
- | + | ||
- | // https:// | + | |
- | // https:// | + | |
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param { ArrayBuffer} vlessBuffer | + | |
- | * @param {string} userID | + | |
- | * @returns | + | |
- | */ | + | |
- | function processVlessHeader( | + | |
- | vlessBuffer, | + | |
- | userID | + | |
- | ) { | + | |
- | if (vlessBuffer.byteLength < 24) { | + | |
- | return { | + | |
- | hasError: | + | |
- | message: ' | + | |
- | }; | + | |
- | } | + | |
- | const version = new Uint8Array(vlessBuffer.slice(0, | + | |
- | let isValidUser = false; | + | |
- | let isUDP = false; | + | |
- | if (stringify(new Uint8Array(vlessBuffer.slice(1, | + | |
- | isValidUser = true; | + | |
- | } | + | |
- | if (!isValidUser) { | + | |
- | return { | + | |
- | hasError: | + | |
- | message: ' | + | |
- | }; | + | |
- | } | + | |
- | + | ||
- | const optLength = new Uint8Array(vlessBuffer.slice(17, | + | |
- | //skip opt for now | + | |
- | + | ||
- | const command = new Uint8Array( | + | |
- | vlessBuffer.slice(18 + optLength, 18 + optLength + 1) | + | |
- | )[0]; | + | |
- | + | ||
- | // 0x01 TCP | + | |
- | // 0x02 UDP | + | |
- | // 0x03 MUX | + | |
- | if (command === 1) { | + | |
- | } else if (command === 2) { | + | |
- | isUDP = true; | + | |
- | } else { | + | |
- | return { | + | |
- | hasError: | + | |
- | message: `command ${command} is not support, command 01-tcp, | + | |
- | }; | + | |
- | } | + | |
- | const portIndex = 18 + optLength + 1; | + | |
- | const portBuffer = vlessBuffer.slice(portIndex, | + | |
- | // port is big-Endian in raw data etc 80 == 0x005d | + | |
- | const portRemote = new DataView(portBuffer).getUint16(0); | + | |
- | + | ||
- | let addressIndex = portIndex + 2; | + | |
- | const addressBuffer = new Uint8Array( | + | |
- | vlessBuffer.slice(addressIndex, | + | |
- | ); | + | |
- | + | ||
- | // 1--> ipv4 addressLength =4 | + | |
- | // 2--> domain name addressLength=addressBuffer[1] | + | |
- | // 3--> ipv6 addressLength =16 | + | |
- | const addressType = addressBuffer[0]; | + | |
- | let addressLength = 0; | + | |
- | let addressValueIndex = addressIndex + 1; | + | |
- | let addressValue = ''; | + | |
- | switch (addressType) { | + | |
- | case 1: | + | |
- | addressLength = 4; | + | |
- | addressValue = new Uint8Array( | + | |
- | vlessBuffer.slice(addressValueIndex, | + | |
- | ).join(' | + | |
- | break; | + | |
- | case 2: | + | |
- | addressLength = new Uint8Array( | + | |
- | vlessBuffer.slice(addressValueIndex, | + | |
- | )[0]; | + | |
- | addressValueIndex += 1; | + | |
- | addressValue = new TextDecoder().decode( | + | |
- | vlessBuffer.slice(addressValueIndex, | + | |
- | ); | + | |
- | break; | + | |
- | case 3: | + | |
- | addressLength = 16; | + | |
- | const dataView = new DataView( | + | |
- | vlessBuffer.slice(addressValueIndex, | + | |
- | ); | + | |
- | // 2001: | + | |
- | const ipv6 = []; | + | |
- | for (let i = 0; i < 8; i++) { | + | |
- | ipv6.push(dataView.getUint16(i * 2).toString(16)); | + | |
- | } | + | |
- | addressValue = ipv6.join(':' | + | |
- | // seems no need add [] for ipv6 | + | |
- | break; | + | |
- | default: | + | |
- | return { | + | |
- | hasError: | + | |
- | message: | + | |
- | }; | + | |
- | } | + | |
- | if (!addressValue) { | + | |
- | return { | + | |
- | hasError: | + | |
- | message: `addressValue is empty, addressType is ${addressType}`, | + | |
- | }; | + | |
- | } | + | |
- | + | ||
- | return { | + | |
- | hasError: false, | + | |
- | addressRemote: | + | |
- | addressType, | + | |
- | portRemote, | + | |
- | rawDataIndex: | + | |
- | vlessVersion: | + | |
- | isUDP, | + | |
- | }; | + | |
- | } | + | |
- | + | ||
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {import(" | + | |
- | * @param {import(" | + | |
- | * @param {ArrayBuffer} vlessResponseHeader | + | |
- | * @param {(() => Promise< | + | |
- | * @param {*} log | + | |
- | */ | + | |
- | async function remoteSocketToWS(remoteSocket, | + | |
- | // remote--> | + | |
- | let remoteChunkCount = 0; | + | |
- | let chunks = []; | + | |
- | /** @type {ArrayBuffer | null} */ | + | |
- | let vlessHeader = vlessResponseHeader; | + | |
- | let hasIncomingData = false; // check if remoteSocket has incoming data | + | |
- | await remoteSocket.readable | + | |
- | .pipeTo( | + | |
- | new WritableStream({ | + | |
- | start() { | + | |
- | }, | + | |
- | /** | + | |
- | * | + | |
- | * @param {Uint8Array} chunk | + | |
- | * @param {*} controller | + | |
- | */ | + | |
- | async write(chunk, | + | |
- | hasIncomingData = true; | + | |
- | // remoteChunkCount++; | + | |
- | if (webSocket.readyState !== WS_READY_STATE_OPEN) { | + | |
- | controller.error( | + | |
- | ' | + | |
- | ); | + | |
- | } | + | |
- | if (vlessHeader) { | + | |
- | webSocket.send(await new Blob([vlessHeader, | + | |
- | vlessHeader = null; | + | |
- | } else { | + | |
- | // seems no need rate limit this, CF seems fix this??.. | + | |
- | // if (remoteChunkCount > 20000) { | + | |
- | // // cf one package is 4096 byte(4kb), | + | |
- | // await delay(1); | + | |
- | // } | + | |
- | webSocket.send(chunk); | + | |
- | } | + | |
- | }, | + | |
- | close() { | + | |
- | log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`); | + | |
- | // safeCloseWebSocket(webSocket); | + | |
- | }, | + | |
- | abort(reason) { | + | |
- | console.error(`remoteConnection!.readable abort`, reason); | + | |
- | }, | + | |
- | }) | + | |
- | ) | + | |
- | .catch((error) => { | + | |
- | console.error( | + | |
- | `remoteSocketToWS has exception `, | + | |
- | error.stack || error | + | |
- | ); | + | |
- | safeCloseWebSocket(webSocket); | + | |
- | }); | + | |
- | + | ||
- | // seems is cf connect socket have error, | + | |
- | // 1. Socket.closed will have error | + | |
- | // 2. Socket.readable will be close without any data coming | + | |
- | if (hasIncomingData === false && retry) { | + | |
- | log(`retry`) | + | |
- | retry(); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {string} base64Str | + | |
- | * @returns | + | |
- | */ | + | |
- | function base64ToArrayBuffer(base64Str) { | + | |
- | if (!base64Str) { | + | |
- | return { error: null }; | + | |
- | } | + | |
- | try { | + | |
- | // go use modified Base64 for URL rfc4648 which js atob not support | + | |
- | base64Str = base64Str.replace(/ | + | |
- | const decode = atob(base64Str); | + | |
- | const arryBuffer = Uint8Array.from(decode, | + | |
- | return { earlyData: arryBuffer.buffer, | + | |
- | } catch (error) { | + | |
- | return { error }; | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * This is not real UUID validation | + | |
- | * @param {string} uuid | + | |
- | */ | + | |
- | function isValidUUID(uuid) { | + | |
- | const uuidRegex = / | + | |
- | return uuidRegex.test(uuid); | + | |
- | } | + | |
- | + | ||
- | const WS_READY_STATE_OPEN = 1; | + | |
- | const WS_READY_STATE_CLOSING = 2; | + | |
- | /** | + | |
- | * Normally, WebSocket will not has exceptions when close. | + | |
- | * @param {import(" | + | |
- | */ | + | |
- | function safeCloseWebSocket(socket) { | + | |
- | try { | + | |
- | if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { | + | |
- | socket.close(); | + | |
- | } | + | |
- | } catch (error) { | + | |
- | console.error(' | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | const byteToHex = []; | + | |
- | for (let i = 0; i < 256; ++i) { | + | |
- | byteToHex.push((i + 256).toString(16).slice(1)); | + | |
- | } | + | |
- | function unsafeStringify(arr, | + | |
- | return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + " | + | |
- | } | + | |
- | function stringify(arr, | + | |
- | const uuid = unsafeStringify(arr, | + | |
- | if (!isValidUUID(uuid)) { | + | |
- | throw TypeError(" | + | |
- | } | + | |
- | return uuid; | + | |
- | } | + | |
- | + | ||
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {import(" | + | |
- | * @param {ArrayBuffer} vlessResponseHeader | + | |
- | * @param {(string)=> | + | |
- | */ | + | |
- | async function handleUDPOutBound(webSocket, | + | |
- | + | ||
- | let isVlessHeaderSent = false; | + | |
- | const transformStream = new TransformStream({ | + | |
- | start(controller) { | + | |
- | + | ||
- | }, | + | |
- | transform(chunk, | + | |
- | // udp message 2 byte is the the length of udp data | + | |
- | // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message | + | |
- | for (let index = 0; index < chunk.byteLength; | + | |
- | const lengthBuffer = chunk.slice(index, | + | |
- | const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); | + | |
- | const udpData = new Uint8Array( | + | |
- | chunk.slice(index + 2, index + 2 + udpPakcetLength) | + | |
- | ); | + | |
- | index = index + 2 + udpPakcetLength; | + | |
- | controller.enqueue(udpData); | + | |
- | } | + | |
- | }, | + | |
- | flush(controller) { | + | |
- | } | + | |
- | }); | + | |
- | + | ||
- | // only handle dns udp for now | + | |
- | transformStream.readable.pipeTo(new WritableStream({ | + | |
- | async write(chunk) { | + | |
- | const resp = await fetch(' | + | |
- | { | + | |
- | method: | + | |
- | headers: | + | |
- | ' | + | |
- | }, | + | |
- | body: chunk, | + | |
- | }) | + | |
- | const dnsQueryResult = await resp.arrayBuffer(); | + | |
- | const udpSize = dnsQueryResult.byteLength; | + | |
- | // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16))); | + | |
- | const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]); | + | |
- | if (webSocket.readyState === WS_READY_STATE_OPEN) { | + | |
- | log(`doh success and dns message length is ${udpSize}`); | + | |
- | if (isVlessHeaderSent) { | + | |
- | webSocket.send(await new Blob([udpSizeBuffer, | + | |
- | } else { | + | |
- | webSocket.send(await new Blob([vlessResponseHeader, | + | |
- | isVlessHeaderSent = true; | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | })).catch((error) => { | + | |
- | log(' | + | |
- | }); | + | |
- | + | ||
- | const writer = transformStream.writable.getWriter(); | + | |
- | + | ||
- | return { | + | |
- | /** | + | |
- | * | + | |
- | * @param {Uint8Array} chunk | + | |
- | */ | + | |
- | write(chunk) { | + | |
- | writer.write(chunk); | + | |
- | } | + | |
- | }; | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * | + | |
- | * @param {string} userID | + | |
- | * @param {string | null} hostName | + | |
- | * @returns {string} | + | |
- | */ | + | |
- | function getVLESSConfig(userID, | + | |
- | const vlessMain = `vless:// | + | |
- | return ` | + | |
- | ################################################################ | + | |
- | v2ray | + | |
- | --------------------------------------------------------------- | + | |
- | ${vlessMain} | + | |
- | --------------------------------------------------------------- | + | |
- | ################################################################ | + | |
- | clash-meta | + | |
- | --------------------------------------------------------------- | + | |
- | - type: vless | + | |
- | name: ${hostName} | + | |
- | server: ${hostName} | + | |
- | port: 443 | + | |
- | uuid: ${userID} | + | |
- | network: ws | + | |
- | tls: true | + | |
- | udp: false | + | |
- | sni: ${hostName} | + | |
- | client-fingerprint: | + | |
- | ws-opts: | + | |
- | path: "/? | + | |
- | headers: | + | |
- | host: ${hostName} | + | |
- | --------------------------------------------------------------- | + | |
- | ################################################################ | + | |
- | `; | + | |
- | } | + | |
- | + | ||
- | </ | + | |
- | + | ||
- | ++++ | + | |
+ | ====== Xray + ipv6 ====== | ||
+ | 不良林的这个文档 [[https:// | ||
====== 夏时 Accelerator ====== | ====== 夏时 Accelerator ====== | ||
行 805: | 行 165: | ||
* [[https:// | * [[https:// | ||
* [[https:// | * [[https:// | ||
+ | |||
+ | {{tag> | ||