Created
May 27, 2023 02:31
-
-
Save cnjax/f38f52503a3b46fdd5b013ffb22c1fa9 to your computer and use it in GitHub Desktop.
thank all tg friends.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
add new ip block | |
63.141.128.0/24 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ../node_modules/uuid/dist/esm-browser/regex.js | |
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; | |
// ../node_modules/uuid/dist/esm-browser/validate.js | |
function validate(uuid) { | |
return typeof uuid === "string" && regex_default.test(uuid); | |
} | |
var validate_default = validate; | |
// ../node_modules/uuid/dist/esm-browser/stringify.js | |
var 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 (!validate_default(uuid)) { | |
throw TypeError("Stringified UUID is invalid"); | |
} | |
return uuid; | |
} | |
var stringify_default = stringify; | |
// vless-js/lib/vless-js.ts | |
var WS_READY_STATE_OPEN = 1; | |
function makeReadableWebSocketStream(ws, earlyDataHeader, log) { | |
let readableStreamCancel = false; | |
return new ReadableStream({ | |
start(controller) { | |
ws.addEventListener("message", async (e) => { | |
if (readableStreamCancel) { | |
return; | |
} | |
const vlessBuffer = e.data; | |
controller.enqueue(vlessBuffer); | |
}); | |
ws.addEventListener("error", (e) => { | |
log("socket has error"); | |
readableStreamCancel = true; | |
controller.error(e); | |
}); | |
ws.addEventListener("close", () => { | |
try { | |
log("webSocket is close"); | |
if (readableStreamCancel) { | |
return; | |
} | |
controller.close(); | |
} catch (error2) { | |
log(`websocketStream can't close DUE to `, error2); | |
} | |
}); | |
const {earlyData, error} = base64ToArrayBuffer(earlyDataHeader); | |
if (error) { | |
log(`earlyDataHeader has invaild base64`); | |
safeCloseWebSocket(ws); | |
return; | |
} | |
if (earlyData) { | |
controller.enqueue(earlyData); | |
} | |
}, | |
pull(controller) { | |
}, | |
cancel(reason) { | |
log(`websocketStream is cancel DUE to `, reason); | |
if (readableStreamCancel) { | |
return; | |
} | |
readableStreamCancel = true; | |
safeCloseWebSocket(ws); | |
} | |
}); | |
} | |
function base64ToArrayBuffer(base64Str) { | |
if (!base64Str) { | |
return {error: null}; | |
} | |
try { | |
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}; | |
} | |
} | |
function safeCloseWebSocket(socket) { | |
try { | |
if (socket.readyState === WS_READY_STATE_OPEN) { | |
socket.close(); | |
} | |
} catch (error) { | |
console.error("safeCloseWebSocket error", error); | |
} | |
} | |
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_default(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]; | |
const command = new Uint8Array( | |
vlessBuffer.slice(18 + optLength, 18 + optLength + 1) | |
)[0]; | |
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); | |
const portRemote = new DataView(portBuffer).getInt16(0); | |
let addressIndex = portIndex + 2; | |
const addressBuffer = new Uint8Array( | |
vlessBuffer.slice(addressIndex, addressIndex + 1) | |
); | |
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) | |
); | |
const ipv6 = []; | |
for (let i = 0; i < 8; i++) { | |
ipv6.push(dataView.getUint16(i * 2).toString(16)); | |
} | |
addressValue = ipv6.join(":"); | |
break; | |
default: | |
console.log(`invild addressType is ${addressType}`); | |
} | |
if (!addressValue) { | |
return { | |
hasError: true, | |
message: `addressValue is empty, addressType is ${addressType}` | |
}; | |
} | |
return { | |
hasError: false, | |
addressType, | |
addressRemote: addressValue, | |
portRemote, | |
rawDataIndex: addressValueIndex + addressLength, | |
vlessVersion: version, | |
isUDP | |
}; | |
} | |
// index.ts | |
import {connect} from "cloudflare:sockets"; | |
// dns.ts | |
const doh = "https://cloudflare-dns.com/dns-query"; | |
const dns = async (domain) => { | |
const aPromise = fetch(`${doh}?name=${domain}&type=A`, { | |
method: "GET", | |
headers: { | |
"Accept": "application/dns-json" | |
} | |
}).then(response => response.json()); | |
const aaaaPromise = fetch(`${doh}?name=${domain}&type=AAAA`, { | |
method: "GET", | |
headers: { | |
"Accept": "application/dns-json" | |
} | |
}).then(response => response.json()); | |
const [aResult, aaaaResult] = await Promise.all([aPromise, aaaaPromise]); | |
const aRecords = aResult && aResult.Answer && aResult.Answer.filter(record => record.type === 1).map(record => record.data); | |
const aaaaRecords = aaaaResult && aaaaResult.Answer && aaaaResult.Answer.filter(record => record.type === 28).map(record => record.data); | |
if (aRecords && aRecords.length > 0) { | |
for (const ip of aRecords) { | |
console.log("request v4 ip" + ip) | |
if (isCloudflareIP(ip)) { | |
console.log("in cloudflare request v4 ip" + ip) | |
return true; | |
} | |
} | |
} | |
if (aaaaRecords && aaaaRecords.length > 0) { | |
for (const ipv6 of aaaaRecords) { | |
console.log("request v6 ip:" + ipv6) | |
if (isCloudflareIPv6(ipv6)) { | |
console.log("in cloudflare,request v6 ip:" + ipv6) | |
return true; | |
} | |
} | |
} | |
return false; | |
}; | |
const isCloudflareIP = (ip) => { | |
const subnets = [ | |
"103.21.244.0/22", | |
"103.22.200.0/22", | |
"103.31.4.0/22", | |
"104.16.0.0/13", | |
"104.24.0.0/14", | |
"108.162.192.0/18", | |
"131.0.72.0/22", | |
"141.101.64.0/18", | |
"162.158.0.0/15", | |
"172.64.0.0/13", | |
"173.245.48.0/20", | |
"188.114.96.0/20", | |
"190.93.240.0/20", | |
"197.234.240.0/22", | |
"198.41.128.0/17", | |
"63.141.128.0/24" | |
]; | |
const ipNum = ip.split(".").reduce((acc, cur) => (acc << 8) + parseInt(cur), 0); | |
for (const subnet of subnets) { | |
const [subnetAddr, maskBits] = subnet.split("/"); | |
const subnetNum = subnetAddr.split(".").reduce((acc, cur) => (acc << 8) + parseInt(cur), 0); | |
const mask = ((1 << maskBits) - 1) << (32 - maskBits); | |
if ((ipNum & mask) === subnetNum) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
const isCloudflareIPv6 = (ip) => { | |
const subnets = [ | |
"2400:cb00::/32", | |
"2606:4700::/32", | |
"2803:f800::/32", | |
"2405:b500::/32", | |
"2405:8100::/32", | |
"2a06:98c0::/29", | |
"2c0f:f248::/32" | |
]; | |
const ipParts = ip.split(":").map(part => parseInt(part, 16)); | |
for (const subnet of subnets) { | |
const [subnetAddr, maskBits] = subnet.split("/"); | |
const subnetParts = subnetAddr.split(":").map(part => parseInt(part, 16)); | |
const mask = ((1 << maskBits) - 1) << (128 - maskBits); | |
let match = true; | |
for (let i = 0; i < 8 && match; i++) { | |
if ((ipParts[i] & mask) !== subnetParts[i]) { | |
match = false; | |
} | |
} | |
if (match) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
// index.ts | |
var HTML404 = "emotional damage"; | |
function delay2(ms) { | |
return new Promise((resolve, rej) => { | |
setTimeout(resolve, ms); | |
}); | |
} | |
var workers_default = { | |
async fetch(request, env, ctx) { | |
let address = ""; | |
let portWithRandomLog = ""; | |
const userID = env.UUID || "624e79ad-0142-4eb1-ac51-e9dfc153d755"; | |
const log = (info, event) => { | |
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ""); | |
}; | |
const upgradeHeader = request.headers.get("Upgrade"); | |
if (!upgradeHeader || upgradeHeader !== "websocket") { | |
return new Response(HTML404, { | |
status: 404, | |
headers: new Headers({"Content-Type": "text/html"}) | |
}); | |
} | |
const webSocketPair = new WebSocketPair(); | |
const [client, webSocket] = Object.values(webSocketPair); | |
const earlyDataHeader = request.headers.get("sec-websocket-protocol") || ""; | |
let remoteSocket = null; | |
webSocket.accept(); | |
const readableWebSocketStream = makeReadableWebSocketStream( | |
webSocket, | |
earlyDataHeader, | |
log | |
); | |
let vlessResponseHeader = new Uint8Array([0, 0]); | |
let remoteConnectionReadyResolve; | |
readableWebSocketStream.pipeTo( | |
new WritableStream({ | |
async write(chunk, controller) { | |
if (remoteSocket) { | |
const writer2 = remoteSocket.writable.getWriter(); | |
await writer2.write(chunk); | |
writer2.releaseLock(); | |
return; | |
} | |
const { | |
hasError, | |
message, | |
portRemote, | |
addressType, | |
addressRemote, | |
rawDataIndex, | |
vlessVersion, | |
isUDP | |
} = processVlessHeader(chunk, userID); | |
address = addressRemote || ""; | |
portWithRandomLog = `${portRemote} -- ${isUDP ? "udp " : "tcp "} `; | |
if (isUDP && portRemote != 53) { | |
controller.error("UDP proxy only enable for DNS which is port 53"); | |
webSocket.close(); | |
return; | |
} | |
if (hasError) { | |
controller.error(message); | |
webSocket.close(); | |
return; | |
} | |
vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); | |
const rawClientData = chunk.slice(rawDataIndex); | |
let queryip = false; | |
log(`remote address:` + addressRemote); | |
if (addressType === 2) { | |
queryip = await dns(addressRemote); | |
if (queryip) { | |
console.log("query cloudflare ip result:" + queryip + " true will rewrite to 192.203.230.x") | |
} | |
} | |
remoteSocket = connect({ | |
hostname: queryip ? "192.203.230." + Math.floor(Math.random() * 255) : addressRemote, | |
port: portRemote | |
}); | |
log(`connected`); | |
const writer = remoteSocket.writable.getWriter(); | |
await writer.write(rawClientData); | |
writer.releaseLock(); | |
remoteConnectionReadyResolve(remoteSocket); | |
}, | |
close() { | |
console.log( | |
`[${address}:${portWithRandomLog}] readableWebSocketStream is close` | |
); | |
}, | |
abort(reason) { | |
console.log( | |
`[${address}:${portWithRandomLog}] readableWebSocketStream is abort`, | |
JSON.stringify(reason) | |
); | |
} | |
}) | |
); | |
(async () => { | |
await new Promise((resolve) => remoteConnectionReadyResolve = resolve); | |
let count = 0; | |
remoteSocket.readable.pipeTo( | |
new WritableStream({ | |
start() { | |
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) { | |
webSocket.send(vlessResponseHeader); | |
} | |
}, | |
async write(chunk, controller) { | |
if (webSocket.readyState === WebSocket.READY_STATE_OPEN) { | |
if (count++ > 2e4) { | |
await delay2(1); | |
} | |
webSocket.send(chunk); | |
} else { | |
controller.error( | |
"webSocket.readyState is not open, maybe close" | |
); | |
} | |
}, | |
close() { | |
console.log( | |
`[${address}:${portWithRandomLog}] remoteConnection!.readable is close` | |
); | |
}, | |
abort(reason) { | |
console.error( | |
`[${address}:${portWithRandomLog}] remoteConnection!.readable abort`, | |
reason | |
); | |
} | |
}) | |
).catch((error) => { | |
console.error( | |
`[${address}:${portWithRandomLog}] processWebSocket has exception `, | |
error.stack || error | |
); | |
safeCloseWebSocket2(webSocket); | |
}); | |
})(); | |
return new Response(null, { | |
status: 101, | |
webSocket: client | |
}); | |
} | |
}; | |
function safeCloseWebSocket2(ws) { | |
try { | |
if (ws.readyState !== WebSocket.READY_STATE_CLOSED) { | |
ws.close(); | |
} | |
} catch (error) { | |
console.error("safeCloseWebSocket error", error); | |
} | |
} | |
export { | |
workers_default as default | |
}; | |
//# sourceMappingURL=index.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment