中国大陆搭建科学上网永久免费节点翻墙,无限流量,永久免费,超清 YouTube 4k 视频稳定流畅,兼容:安卓、windows、IOS、Mac OS 设备使用!
一、准备工作
windows电脑下载最新版V2rayN代理客户端:【点击直接下载】或作者仓库地址:【点此打开】
安卓(Android)手机下载V2rayNG,可在谷歌应用商店下载或【点此直接下载】或作者仓库地址:【点此打开】
IOS与Mac OS(M芯片)用户使用最新版本Shadowrocket(小火箭)客户端导入使用,如没有IOS小火箭代理客户端的用户请到:https://sudukj.eu.org (官网)进行购买,如有任何售前售后问题,请点击官网右上角客服进行咨询!客服每天工作时间段都在线解答!
二、注册cloudflare账号
1、cloudflare官网:【点此打开】
2、注册cloudflare账号如下图或如视频教程
三、部署Workers代码
1、Workers代码
// <!--GAMFC-->version base on commit 841ed4e9ff121dde0ed6a56ae800c2e6c4f66056, time is 2024-04-16 18:02:37 UTC<!--GAMFC-END-->.
// @ts-ignore
import { connect } from 'cloudflare:sockets';
// How to generate your own UUID:
// [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()"
let userID = '填入你的UUID';
let proxyIP = '填入你的优选IP';
if (!isValidUUID(userID)) {
throw new Error('uuid is not valid');
}
export default {
/**
* @param {import("@cloudflare/workers-types").Request} request
* @param {{UUID: string, PROXYIP: string}} env
* @param {import("@cloudflare/workers-types").ExecutionContext} ctx
* @returns {Promise<Response>}
*/
async fetch(request, env, ctx) {
try {
userID = env.UUID || userID;
proxyIP = env.PROXYIP || proxyIP;
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
const url = new URL(request.url);
switch (url.pathname) {
case '/':
return new Response(JSON.stringify(request.cf), { status: 200 });
case `/${userID}`: {
const vlessConfig = getVLESSConfig(userID, request.headers.get('Host'));
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
}
default:
return new Response('Not found', { status: 404 });
}
} else {
return await vlessOverWSHandler(request);
}
} catch (err) {
/** @type {Error} */ let e = err;
return new Response(e.toString());
}
},
};
/**
*
* @param {import("@cloudflare/workers-types").Request} request
*/
async function vlessOverWSHandler(request) {
/** @type {import("@cloudflare/workers-types").WebSocket[]} */
// @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}:${portWithRandomLog}] ${info}`, event || '');
};
const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';
const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);
/** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/
let remoteSocketWapper = {
value: null,
};
let udpStreamWrite = null;
let isDns = false;
// ws --> remote
readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) {
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, 0]),
isUDP,
} = processVlessHeader(chunk, userID);
address = addressRemote;
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
} `;
if (hasError) {
// controller.error(message);
throw new Error(message); // cf seems has bug, controller.error will not end stream
// webSocket.close(1000, message);
return;
}
// if UDP but port not DNS port, close it
if (isUDP) {
if (portRemote === 53) {
isDns = true;
} else {
// controller.error('UDP proxy only enable for DNS which is port 53');
throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream
return;
}
}
// ["version", "附加信息长度 N"]
const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
const rawClientData = chunk.slice(rawDataIndex);
// TODO: support udp here when cf runtime has udp support
if (isDns) {
const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log);
udpStreamWrite = write;
udpStreamWrite(rawClientData);
return;
}
handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log);
},
close() {
log(`readableWebSocketStream is close`);
},
abort(reason) {
log(`readableWebSocketStream is abort`, JSON.stringify(reason));
},
})).catch((err) => {
log('readableWebSocketStream pipeTo error', err);
});
return new Response(null, {
status: 101,
// @ts-ignore
webSocket: client,
});
}
/**
* 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("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to.
* @param {Uint8Array} vlessResponseHeader The VLESS response header.
* @param {function} log The logging function.
* @returns {Promise<void>} The remote socket.
*/
async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
async function connectAndWrite(address, port) {
/** @type {import("@cloudflare/workers-types").Socket} */
const tcpSocket = connect({
hostname: address,
port: port,
});
remoteSocket.value = tcpSocket;
log(`connected to ${address}:${port}`);
const writer = tcpSocket.writable.getWriter();
await writer.write(rawClientData); // first write, nomal is tls client hello
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, portRemote)
// no matter retry success or not, close websocket
tcpSocket.closed.catch(error => {
console.log('retry tcpSocket closed error', error);
}).finally(() => {
safeCloseWebSocket(webSocket);
})
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log);
}
const tcpSocket = await connectAndWrite(addressRemote, portRemote);
// when remoteSocket is ready, pass to websocket
// remote--> ws
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}
/**
*
* @param {import("@cloudflare/workers-types").WebSocket} webSocketServer
* @param {string} earlyDataHeader for ws 0rtt
* @param {(info: string)=> void} log for ws 0rtt
*/
function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) {
let readableStreamCancel = false;
const stream = new ReadableStream({
start(controller) {
webSocketServer.addEventListener('message', (event) => {
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('close', () => {
// client send close, need close server
// if stream is cancel, skip controller.close
safeCloseWebSocket(webSocketServer);
if (readableStreamCancel) {
return;
}
controller.close();
}
);
webSocketServer.addEventListener('error', (err) => {
log('webSocketServer has error');
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/#example-rs-push-backpressure
},
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/enqueue need skip,
// 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://xtls.github.io/development/protocols/vless.html
// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw
/**
*
* @param { ArrayBuffer} vlessBuffer
* @param {string} userID
* @returns
*/
function processVlessHeader(
vlessBuffer,
userID
) {
if (vlessBuffer.byteLength < 24) {
return {
hasError: true,
message: 'invalid data',
};
}
const version = new Uint8Array(vlessBuffer.slice(0, 1));
let isValidUser = false;
let isUDP = false;
if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
isValidUser = true;
}
if (!isValidUser) {
return {
hasError: true,
message: 'invalid user',
};
}
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
//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: true,
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
};
}
const portIndex = 18 + optLength + 1;
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
// 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, addressIndex + 1)
);
// 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, addressValueIndex + addressLength)
).join('.');
break;
case 2:
addressLength = new Uint8Array(
vlessBuffer.slice(addressValueIndex, addressValueIndex + 1)
)[0];
addressValueIndex += 1;
addressValue = new TextDecoder().decode(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
break;
case 3:
addressLength = 16;
const dataView = new DataView(
vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)
);
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
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: true,
message: `invild addressType is ${addressType}`,
};
}
if (!addressValue) {
return {
hasError: true,
message: `addressValue is empty, addressType is ${addressType}`,
};
}
return {
hasError: false,
addressRemote: addressValue,
addressType,
portRemote,
rawDataIndex: addressValueIndex + addressLength,
vlessVersion: version,
isUDP,
};
}
/**
*
* @param {import("@cloudflare/workers-types").Socket} remoteSocket
* @param {import("@cloudflare/workers-types").WebSocket} webSocket
* @param {ArrayBuffer} vlessResponseHeader
* @param {(() => Promise<void>) | null} retry
* @param {*} log
*/
async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
// remote--> ws
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, controller) {
hasIncomingData = true;
// remoteChunkCount++;
if (webSocket.readyState !== WS_READY_STATE_OPEN) {
controller.error(
'webSocket.readyState is not open, maybe close'
);
}
if (vlessHeader) {
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
vlessHeader = null;
} else {
// seems no need rate limit this, CF seems fix this??..
// if (remoteChunkCount > 20000) {
// // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M
// await delay(1);
// }
webSocket.send(chunk);
}
},
close() {
log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`);
// safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway.
},
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(/-/g, '+').replace(/_/g, '/');
const decode = atob(base64Str);
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
return { earlyData: arryBuffer.buffer, error: null };
} catch (error) {
return { error };
}
}
/**
* This is not real UUID validation
* @param {string} uuid
*/
function isValidUUID(uuid) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
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("@cloudflare/workers-types").WebSocket} socket
*/
function safeCloseWebSocket(socket) {
try {
if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) {
socket.close();
}
} catch (error) {
console.error('safeCloseWebSocket error', error);
}
}
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!isValidUUID(uuid)) {
throw TypeError("Stringified UUID is invalid");
}
return uuid;
}
/**
*
* @param {import("@cloudflare/workers-types").WebSocket} webSocket
* @param {ArrayBuffer} vlessResponseHeader
* @param {(string)=> void} log
*/
async function handleUDPOutBound(webSocket, vlessResponseHeader, log) {
let isVlessHeaderSent = false;
const transformStream = new TransformStream({
start(controller) {
},
transform(chunk, controller) {
// 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, index + 2);
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('https://1.1.1.1/dns-query',
{
method: 'POST',
headers: {
'content-type': 'application/dns-message',
},
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, dnsQueryResult]).arrayBuffer());
} else {
webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer());
isVlessHeaderSent = true;
}
}
}
})).catch((error) => {
log('dns udp has error' + error)
});
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, hostName) {
const vlessMain = `vless://${userID}\u0040${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`
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: chrome
ws-opts:
path: "/?ed=2048"
headers:
host: ${hostName}
---------------------------------------------------------------
################################################################
`;
}
2、UUID在线生成网站
https://1024tools.com/uuid
https://www.bejson.com/encrypt/genuuid
3、在线优选IP站
https://stock.hostmonit.com/CloudFlareYes
4、优选域名推荐
time.cloudflare.com
shopify.com
time.is
icook.hk
icook.tw
ip.sb
japan.com
malaysia.com
russia.com
singapore.com
skk.moe
www.visa.com
www.visa.com.sg
www.visa.com.hk
www.visa.com.tw
www.visa.co.jp
www.visakorea.com
www.gco.gov.qa
www.gov.se
www.gov.ua
www.digitalocean.com
www.csgo.com
www.shopify.com
www.whoer.net
www.whatismyip.com
www.ipget.net
www.hugedomains.com
www.udacity.com
www.4chan.org
www.okcupid.com
www.glassdoor.com
www.udemy.com
www.baipiao.eu.org
cdn.anycast.eu.org
cdn-all.xn--b6gac.eu.org
cdn-b100.xn--b6gac.eu.org
xn--b6gac.eu.org
edgetunnel.anycast.eu.org
alejandracaiccedo.com
nc.gocada.co
log.bpminecraft.com
www.boba88slot.com
gur.gov.ua
www.zsu.gov.ua
www.iakeys.com
edtunnel-dgp.pages.dev
www.d-555.com
fbi.gov
workers.cloudflare.cyou
acjp2.cloudflarest.link
acsg.cloudflarest.link
acsg3.cloudflarest.link
acjp2.cloudflarest.link
acsg3.cloudflarest.link
acsg3.cloudflarest.link
acsg.cloudflarest.link
5、可自建多个节点,节点端口分别为:2053
2087
2052
2082
8443
2083
2096
2095
详细操作请参照教学视频
免责声明:
- 以上分享的内容中涉及的任何解锁和解密分析脚本仅供资源共享和学习研究,不能保证合法性、准确性、完整性和有效性,请根据实际情况自行判断。
- 以上分享的内容来源网络,请大家自行判断使用,包括但不限于由任何内容错误导致的任何损失或损害, 梦歌不承担任何责任。
- 你必须在下载后24小时内从您的计算机或手机中彻底删除以上所分享的全部内容。
- 如果任何单位或个人认为以上分享的内容可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关脚本。
- 使用者都应仔细阅读此声明,梦歌保留随时更改或补充此免责声明的权利。您一旦使用并复制了本项目分享的任何内容,则视为您已接受此免责声明。
Disclaimer:
- Any unlocking and decryption analysis scripts involved in the content shared above are only for resource sharing and study and research, and cannot guarantee legality, accuracy, completeness and validity. Please judge by yourself according to the actual situation.
- The content shared above comes from the Internet, please judge and use it by yourself, including but not limited to any loss or damage caused by any content error, Mengge does not assume any responsibility.
- You must completely delete all content shared above from your computer or mobile phone within 24 hours of downloading.
- If any unit or individual believes that the content shared above may be suspected of violating its rights, it should notify in time and provide proof of identity and ownership. We will delete the relevant scripts after receiving the certification documents.
- Users should read this statement carefully, Mengge reserves the right to change or supplement this disclaimer at any time. Once you use and copy any content shared by this project, you are deemed to have accepted this disclaimer.
🔴版權聲明及使用限制 本頻道所提供的內容、連結、教學課程和資源,僅供非中國大陸地區觀眾學習與交流之用。 在使用這些內容之前,請務必確保您遵守您所在國家或地區的法律法規。 任何這些內容的使用均需符合您所在地的法律規定。
🔴嚴禁違法 特此申明,嚴禁將本頻道內容在中國大陸地區傳播。 任何在中國大陸地區傳播本頻道內容的行為,與本頻道的經營者無關。
🔴免責聲明 本頻道不對您使用內容所產生的後果承擔任何法律或道德責任。 使用本頻道內容時,您需自行承擔風險並遵守當地法規。 經營者不對內容在中國大陸地區的任何使用負責。
🔴同意條款 您使用本頻道內容即表示您已閱讀、瞭解並同意遵守上述免責聲明。 若您對內容有任何版權異議,請透過信箱聯絡我們處理。
❤️感謝您的理解與配合。
🔴Copyright Statement and Usage Restrictions The content, links, teaching courses and resources provided by this channel are only for learning and communication purposes for viewers outside of mainland China. Please make sure you comply with the laws and regulations of your country or region before using this content. Use of any such content must comply with the laws of your location.
🔴Illegal laws are strictly prohibited. We hereby declare that dissemination of the contents of this channel in mainland China is strictly prohibited. Any dissemination of the content of this channel in mainland China has nothing to do with the operator of this channel.
🔴Disclaimer This channel does not assume any legal or moral responsibility for the consequences of your use of the content. When using the content on this channel, you do so at your own risk and in compliance with local regulations. The operator is not responsible for any use of the content in mainland China.
🔴Agree to the Terms Your use of the content on this channel means that you have read, understood and agreed to abide by the above disclaimer. If you have any copyright objections to the content, please contact us via email.
❤️Thank you for your understanding and cooperation.
#免费翻墙 #免费vpn #永久免费vpn #免费机场 #免费4k机场 #高速机场 #高速节点 #4k机场 #机场推荐 #快速翻墙 #免费科学上网 #免费ssr节点 #免费节点 #高速节点推荐 #ssr机场 #翻墙 #科学上网 #公益机场 #ios翻墙 #vpn #v2ray #v2ray机场 #ssr vpn #v2ray vpn #共享机场 #美国翻墙节点 #美国科学上网ip #免费v2ray节点 #v2ray 机场 #v2ray 节点订阅 #免费稳定高速节点 #trojan科学上网 #trojan全平台 #科学上网 v2ray #trojan翻墙 #永久免费 #翻墙方法 #机场节点订阅 #手机科学上网 #电脑科学上网 #vpn注册 #vpn使用 #ssr节点 #vpn节点 #4k ssr节点 #免费v2ray #v2ray二维码 #v2ray订阅 #翻墙教程 #科学翻墙 #vpn永久可用 #加速器vpn #加速器vpn ios #加速器安卓 #ssr被墙 #v2ray被墙 #永远不被墙 #稳定的科学上网方式 #kaspersky vpn #clash #免费clash #免费clash节点 #clash订阅 #梦歌 #winxray #winxray订阅 #免费订阅 #节点 #梯子