Use V2rayN (V2ray Client) to Protect Your Online Privacy & Bypass Internet Censorship - NETSEC

Latest

Learning, Sharing, Creating

Cybersecurity Memo

Sunday, November 3, 2024

Use V2rayN (V2ray Client) to Protect Your Online Privacy & Bypass Internet Censorship

V2Ray, short for "V2-Project Ray," is an open-source network protocol that aims to provide users with enhanced privacy, security, and anonymity while navigating the digital realm.  Essentially, V2Ray serves as a versatile tool for bypassing censorship, encrypting internet traffic, and accessing geo-restricted content. Unlike traditional VPNs and proxies, V2Ray operates on a more sophisticated level, offering a broader range of features and customization options.

V2rayN is a Windows client for V2Ray. It is a graphical user interface (GUI) for V2Ray, a tool for bypassing internet censorship. It provides users with an easy-to-use interface to access and manage their V2Ray servers. V2rayN is available for free and is open source.

V2rayN provides a few features that make it easier to use V2Ray. It allows users to quickly switch between different servers, set up custom routing rules, and monitor their connection status. It also provides a few additional features such as automatic connection and the ability to export and import configuration files.


 
Github: https://github.com/51sec/ss_generator

V2rayN Features

Supports V2Ray, Shadowsocks, Trojan and VMess protocols. Offers support for multiple servers and custom routing rules. Configuration file import and export support. Supports many different obfuscation methods like HTTP/2, QUIC and WebSocket.

Github: https://github.com/2dust/v2rayN

Vless protocol:

Note: https://yugogo.xyz/?p=847 (V2ray最强配置Vless+Tcp+Xtls+Fallback通信原理终极大解密,秒懂v2ray各项配置技术)

How to Download and Which one to Download


1 Go to release page

Release Page: Github release page V2rayN


Or other download page:

v2fly/xray Clients

clash 客户端

  • clash Windows客户端 clashN

    clashN



2 You can choose pre-release version or latest release version

Click version number, here we are using version 7.0.6 as an example



3 Select the right file to download. 

I am using Windows OS, and usually go with v2rayN-windows-64-SelfContained-With-Core.7z



4 Download and extract the file. Double click  v2rayN.exe file to start this V2ray client. 




Get free web service from sshOcean


sshOcean provides free SSH and VPN accounts, with fast and premium server, which make Your Connection More Secure and Unblock All Sites With Free / Premium  SSH and  VPN Accounts.

It provides following free services:
Depends on what you are doing and what you already installed, you can choose those services accordingly. 

Lets go with V2Ray VLESS. VLESS is the next generation of V2RAY with more advantages like lightweight transmission protocol, the encryption is more flexible, and using UUID for authentication method without alterId. in some condition, VLESS is faster than VMESS. VLESS is also available on Windows, macOS, BSD, iOS, and Android.

You will find out quite a few VLESS server location caterized by Countries. Depends on where you would like to connect to for unlocking IP or Geographic restriction, you can choose any one of them to generate your free VLESS account. 



This is the page after clicked HONGKONG :


You can create an account then get VLESS link to copy into your clipboard.



Creae a server in V2rayN

 Since we have copied our v2ray VLESS link, we can choose import shared links from Clipboard this option:



Switch Enable Tun to on, and choose System proxy to set system proxy


Configuration successful
[VLESS] sshocean-netsec.cagmail.com-ws(hu2***site:443)
Microsoft Windows NT 10.0.19045.0 - 64
Start service (2024/11/03 14:15:06)...
Xray 24.10.31 (Xray, Penetrates Everything.) 4ec5c78 (go1.23.2 windows/amd64)
A unified platform for anti-censorship.
2024/11/03 14:15:06 Using default config:  C:\Users\User\Downloads\v2rayN-windows-64-SelfContained-With-Core\v2rayN-windows-64-SelfContained-With-Core\guiConfigs\config.json
2024/11/03 14:15:06 [Info] infra/conf/serial: Reading config: &{Name:C:\Users\User\Downloads\v2rayN-windows-64-SelfContained-With-Core\v2rayN-windows-64-SelfContained-With-Core\guiConfigs\config.json Format:json}
2024/11/03 14:15:07 [Warning] core: Xray 24.10.31 started
2024/11/03 14:15:07 from 127.0.0.1:4018 accepted //www.google.com:443 [http -> proxy]
2024/11/03 14:15:09 The ping of current service: 112 ms
Configuration successful

[VLESS] sshocean-netsec.cagmail.com-ws(hu2***site:443)
Microsoft Windows NT 10.0.19045.0 - 64
Start service (2024/11/03 14:15:15)...
Xray 24.10.31 (Xray, Penetrates Everything.) 4ec5c78 (go1.23.2 windows/amd64)
A unified platform for anti-censorship.
2024/11/03 14:15:15 Using default config:  C:\Users\User\Downloads\v2rayN-windows-64-SelfContained-With-Core\v2rayN-windows-64-SelfContained-With-Core\guiConfigs\config.json
2024/11/03 14:15:15 [Info] infra/conf/serial: Reading config: &{Name:C:\Users\User\Downloads\v2rayN-windows-64-SelfContained-With-Core\v2rayN-windows-64-SelfContained-With-Core\guiConfigs\config.json Format:json}
2024/11/03 14:15:16 [Warning] core: Xray 24.10.31 started
2024/11/03 14:15:17 from 127.0.0.1:4037 accepted //www.google.com:443 [http -> proxy]
2024/11/03 14:15:18 from udp:127.0.0.1:54581 accepted udp:8.8.8.8:53 [socks -> proxy]
2024/11/03 14:15:19 The ping of current service: 245 ms
2024/11/03 14:15:19 System proxy setting is changed - ForcedChange
2024/11/03 14:15:19 from 127.0.0.1:9615 accepted //mtalk.google.com:5228 [http -> proxy]
2024/11/03 14:15:20 from 127.0.0.1:9634 accepted //teams.events.data.microsoft.com:443 [http -> proxy]


After done, you can set back the settings of "System Proxy" to Clear system proxy.


Use Google Colab to Generate SS Nodes

sshocean can generate multiple types of secure proxy / vpn servers. But each time you only can get one server. Here is a way you can generate 12 different SS (Shadowsocks) servers at one time. 

Shadowsocks is a secure split proxy loosely based on SOCKS5. The Shadowsocks local component (ss-local) acts like a traditional SOCKS5 server and provides proxy service to clients. It encrypts and forwards data streams and packets from the client to the Shadowsocks remote component (ss-remote), which decrypts and forwards to the target. Replies from target are similarly encrypted and relayed by ss-remote back to ss-local, which decrypts and eventually returns to the original client.

We have multiple ways to use online free web services to generate this list. Here is how to do it from Google Colab. 

1 Go to Google Colab website

You will need a Google Account

2 Add code and run to install dependencies

!pip install pyaes




3 Add following code in then run it to generate SS nodes
  • Github: https://github.com/51sec/ss_generator

import requests
import base64
import json
import pyaes
import binascii
from datetime import datetime
a = 'http://api.skrapp.net/api/serverlist'
b = {
    'accept': '/',
    'accept-language': 'zh-Hans-CN;q=1, en-CN;q=0.9',
    'appversion': '1.3.1',
    'user-agent': 'SkrKK/1.3.1 (iPhone; iOS 13.5; Scale/2.00)',
    'content-type': 'application/x-www-form-urlencoded',
    'Cookie': 'PHPSESSID=fnffo1ivhvt0ouo6ebqn86a0d4'
}
c = {'data': '4265a9c353cd8624fd2bc7b5d75d2f18b1b5e66ccd37e2dfa628bcb8f73db2f14ba98bc6a1d8d0d1c7ff1ef0823b11264d0addaba2bd6a30bdefe06f4ba994ed'}
d = b'65151f8d966bf596'
e = b'88ca0f0ea1ecf975'
def f(g, d, e):
    h = pyaes.AESModeOfOperationCBC(d, iv=e)
    i = b''.join(h.decrypt(g[j:j+16]) for j in range(0, len(g), 16))
    return i[:-i[-1]]
j = requests.post(a, headers=b, data=c)
if j.status_code == 200:
    k = j.text.strip()
    l = binascii.unhexlify(k)
    m = f(l, d, e)
    n = json.loads(m)
    print("youtube.com/netsec")
    for o in n['data']:
        p = f"aes-256-cfb:{o['password']}@{o['ip']}:{o['port']}"
        q = base64.b64encode(p.encode('utf-8')).decode('utf-8')
        r = f"ss://{q}#{o['title']}"
        print(r)




4 Create a Subscription Group from v2rayN





5 Change CoreType settings for Shadowsocks
Change it to sing_box




6 Go back to Group1 (Created at step 4) 
ctrl+v to paste all SS nodes info into Group1

You can choose any of records to set it as active server. 

Enable Tun: Route all your machine traffic to the v2rayN tunnel
Set System Proxy: Enable Windows proxy to router any application traffic Using Windows proxy to the v2ray tunnel.



Now when you browse to Internet, it will use that active server ip as your public ip. 


Use Replit.com to Generate Server List

 Log into Replit.com

Create a Python Replit


Choose Python Template



Copy following code in

  • Github: https://github.com/51sec/ss_generator
import requests
import base64
import json
import pyaes
import binascii
from datetime import datetime
a = 'http://api.skrapp.net/api/serverlist'
b = {
    'accept': '/',
    'accept-language': 'zh-Hans-CN;q=1, en-CN;q=0.9',
    'appversion': '1.3.1',
    'user-agent': 'SkrKK/1.3.1 (iPhone; iOS 13.5; Scale/2.00)',
    'content-type': 'application/x-www-form-urlencoded',
    'Cookie': 'PHPSESSID=fnffo1ivhvt0ouo6ebqn86a0d4'
}
c = {'data': '4265a9c353cd8624fd2bc7b5d75d2f18b1b5e66ccd37e2dfa628bcb8f73db2f14ba98bc6a1d8d0d1c7ff1ef0823b11264d0addaba2bd6a30bdefe06f4ba994ed'}
d = b'65151f8d966bf596'
e = b'88ca0f0ea1ecf975'
def f(g, d, e):
    h = pyaes.AESModeOfOperationCBC(d, iv=e)
    i = b''.join(h.decrypt(g[j:j+16]) for j in range(0, len(g), 16))
    return i[:-i[-1]]
j = requests.post(a, headers=b, data=c)
if j.status_code == 200:
    k = j.text.strip()
    l = binascii.unhexlify(k)
    m = f(l, d, e)
    n = json.loads(m)
    print("youtube.com/netsec")
    for o in n['data']:
        p = f"aes-256-cfb:{o['password']}@{o['ip']}:{o['port']}"
        q = base64.b64encode(p.encode('utf-8')).decode('utf-8')
        r = f"ss://{q}#{o['title']}"
        print(r)



Run to generate the server list




Use Cloudflare to Generate vless nodes

 Github project: 
  • https://github.com/51sec/ss_generator/blob/main/worker2vless.js
  • Original one: https://github.com/zizifn/edgetunnel/tree/main/src

Create a new worker using js code in the Github project. You can use Cloudflare own workers sub-domain. 

You can use the code from Github or directly copy following code into your Cloudflare workers:

// <!--GAMFC-->version base on commit 5a112a0a0994b8bb834427ac84133501407f6413, time is 2024-10-11 04:49:19 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 = 'd342d11e-d424-4583-b36e-524ab1f0afa4';
let proxyIP = '';

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}
---------------------------------------------------------------
################################################################
`;
}



You also can use a group of random ProxyIP instead of single one. 

  1. const proxyIPs = ['cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'workers.cloudflare.cyou'];
  2.  
  3. // if you want to use ipv6 or single proxyIP, please add comment at this line and remove comment at the next line
  4. let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
  5. // use single proxyIP instead of random
  6. // let proxyIP = 'cdn.xn--b6gac.eu.org';
  7. // ipv6 proxyIP example remove comment to use
  8. // let proxyIP = "[2a01:4f8:c2c:123f:64:5:6810:c55a]"





Two things you might want to change:
1. UUID
2. Proxyip
For proxy ip, in the original github code, it is empty, but we can choose one from following proxyIP list maintained by someone else on Internet.
  1. CM 维护
  2. proxyip.us.fxxk.dedyn.io
  3. IP落地区域: 美国 维护频率: 12小时/次
  4. proxyip.sg.fxxk.dedyn.io
  5. IP落地区域: 新加坡 维护频率: 12小时/次
  6. proxyip.jp.fxxk.dedyn.io
  7. IP落地区域: 日本 维护频率: 12小时/次
  8. proxyip.hk.fxxk.dedyn.io
  9. IP落地区域: 香港 维护频率: 12小时/次
  10. proxyip.aliyun.fxxk.dedyn.io
  11. IP落地区域: 阿里云 维护频率: 4小时/次
  12. proxyip.oracle.fxxk.dedyn.io
  13. IP落地区域: 甲骨文 维护频率: 4小时/次
  14. proxyip.digitalocean.fxxk.dedyn.io
  15. IP落地区域: 数码海 维护频率: 4小时/次
  16.  
  17. 白嫖哥维护
  18. workers.cloudflare.cyou
  19.  
  20. Mingyu维护
  21. my-telegram-is-herocore.onecf.eu.org
  22. sg.ipdb.rr.nu
  23. nl.ipdb.rr.nu
  24. hk.ipdb.rr.nu
  25. jp.ipdb.rr.nu
  26. us.ipdb.rr.nu
  27.  
  28. 小一维护
  29. hk.cf.zhetengsha.eu.org
  30. sg.cf.zhetengsha.eu.org
  31. us.cf.zhetengsha.eu.org
  32. jp.cf.zhetengsha.eu.org
More: 
大佬们搭建好的订阅器(生成器)
  • aliyun.classelivre.eu.org 天城大佬
  • cm.godns.onflashdrive.app 天城大佬
  • 3k.fxxk.dedyn.io 3K大佬 
  • vmess.fxxk.dedyn.io CM大佬
  • vless.fxxk.dedyn.io CM大佬
订阅器使用格式:https://生成器地址(订阅器)/sub?host=伪装域名&uuid=你的UUID&path=路径

For example:
https://v.51sec.workers.dev/sub?host=v.51sec.workers.dev&decade00-0000-4000-a000-000000000000&path=/?ed=2048


伪装域名 (host)
  • in my example, it is same as 优选IP (Address)
  • It can be different and some other hosts on internet
    • such as th.amazinglinyy.workers.dev


优选IP (Address)
  • in my example, it is same as 伪装域名 (host)
  • But it can be different.
    • such as www.visa.com:8880 to replace my v.51sec.workers.dev:443



After you deployed successfully, you can access your worker with uuid (https://<subdomain name>/UUID)

You also can assign your own subdomain to this worker then use your subdomain to access it with uuid. 
CTRL+A to select all, then CTRL+C. CTRL+V paste into v2rayN. 


Re-generate uuid in v2rayN:

Note: generate a sub link from above screenshot: 
For example:
https://v.51sec.workers.dev/sub?host=v.51sec.workers.dev&decade00-0000-4000-a000-000000000000&path=/?ed=2048
You can add this link to v2rayN's sub group to get a bunch of links. Then you will find a best optimized link with an ip inside to use. 

Note: Chagne Address (v.51sec.workers.dev) to a optimized IP:

将复制的 V2ray 配置链接粘贴到 V2rayN 客户端,地址填入优选 IP ,即可成功科学上网。优选 IP 可通过 https://stock.hostmonit.com/CloudFlareYes.


Note:  A simple and easy to remember uuid:
  • decade00-0000-4000-a000-000000000000

Output for https://v.51sec.workers.dev/decade00-0000-4000-a000-000000000000

################################################################
v2ray
---------------------------------------------------------------
vless://[email protected]:443?encryption=none&security=tls&sni=v.51sec.workers.dev&fp=randomized&type=ws&host=v.51sec.workers.dev&path=%2F%3Fed%3D2048#v.51sec.workers.dev
---------------------------------------------------------------
################################################################
clash-meta
---------------------------------------------------------------
- type: vless
  name: v.51sec.workers.dev
  server: v.51sec.workers.dev
  port: 443
  uuid: decade00-0000-4000-a000-000000000000
  network: ws
  tls: true
  udp: false
  sni: v.51sec.workers.dev
  client-fingerprint: chrome
  ws-opts:
    path: "/?ed=2048"
    headers:
      host: v.51sec.workers.dev
---------------------------------------------------------------
################################################################




Note:https://v2rayssr.com/worker-vless.html



Performance Check


Alternatively, you can check the latency via HTTPS request from your browser (different than ping) and verify if your ISP is not blocked or if you will be bypassing the firewall following the URL:

Ping latency? Open your terminal/cmd and run: ping falkenstein-lb-1.vs.secologist.com



Videos

 

Using V2rayN Free V2ray Client to Protect Your Privacy and Bypass Internet Censorship


Generate SS (Shadowsocks tunnel proxy) Server List for V2rayN (Free & Easy)






References



No comments:

Post a Comment