标签 Cloudflare 下的文章 - 🥝 E 家 分 享 🥝
首页
📋 留言板
🔗 友情链接
🛠️ E家百宝箱
❤️ 关于
推荐
🔍 VPS监控
🐉 青龙面板
💽 E家网盘
----------
🔗 CloudFlare
🔗 甲骨文云
🔗 RackNerd
搜 索
1
自动提取 ChromeGo 一键翻墙包内的免费节点
145 阅读
2
【汇总:免费节点 - 每周更新】
114 阅读
3
GigaFile - 日本免费大文件加密分享服务,最长保留文档100天
71 阅读
4
【E家分享月刊系列】2024-12
56 阅读
5
CloudFlare WARP 免费 VPN 搭建教程
55 阅读
精选网站
网站搭建
科学上网搭建
有感而发
软件技巧
Excel技巧
WordPress技巧
登录
搜 索
标签搜索
WordPress
脚本
GitHub
科学上网
哈佛管理导师
E家分享月刊系列
V2ray
Mac软件
AI
Cloudflare
Docker
免费节点
建站在线工具
Excel技巧
Notion
Nginx
ChatGPT
图像编辑
免费图床
网盘资源
E家之长
累计撰写
195
篇文章
累计收到
245
条评论
首页
栏目
精选网站
网站搭建
科学上网搭建
有感而发
软件技巧
Excel技巧
WordPress技巧
页面
📋 留言板
🔗 友情链接
🛠️ E家百宝箱
❤️ 关于
推荐
🔍 VPS监控
🐉 青龙面板
💽 E家网盘
----------
🔗 CloudFlare
🔗 甲骨文云
🔗 RackNerd
用户登录
登录
找到
7
篇与
Cloudflare
相关的结果
2024-01-13
【E家原创】利用 Slicehosting 搭建免费科学上网节点
前言本文介绍利用 Slicehosting 空间搭建免费科学上网节点,同时解锁 Netflix,Chatgpt。注册并登录 Slicehosting打开 Slicehosting 官网地址:https://dash.slicehosting.tech/ ,点击"Login with Discord“按钮进行注册。如果没有 Discord 帐户,就会跳转到 Discord 网站进行注册。如果已经有帐户,那么授权页面,点击”授权“按钮即可完成注册和登录。创建服务器进入控制面板,点击‘Create a server”按钮,依照以下步骤创建服务器。只需输入服务器名称,选择服务器地址,选择服务器软件,即可创建成功。CPU 默认占用是50%,所以 Slicehosting 默认可以创建2个地区的服务器。选择其中一个服务器,点击“Open”进入服务器,开始搭建节点。上传节点代码文件第一次进入后台,会弹出输入用户名和密码的对话框。如下图:按照下面步骤,点击左侧的“Game panel access”,重置密码。重置后的密码只出行一次,请复制后妥善保存。回到登录界面,输入 Email 地址和重置后的密码,即可登入控制面板。 按照下面步骤,进入“Flie”页面,删除 server.jar 文件。打开搭建节点代码地址:https://github.com/dsadsadsss/java-wanju ,下载并上传 c.yml 和 server.jar 这两个文件。点击打开 c.yml 文件,需要修改3个地方的信息,分别是自己的 ARGO 域名,及其 CloudFlare 的 Token 值和 UUID。CloudFlare 创建隧道及获取 Token 值登录 CloudFlare,点击左侧的“Zero Trust”。第一次打开会提示创建一个 Team 名称,后面也可以修改该名称。选择免费方案并确认付款。依次选择“Access”,“Tunnels”,创建一个新的隧道。复制生成的 Token。创建自定义域名回到隧道列表,点击右侧的三个点,选择“Configure”。选择“Public Hosting”,创建一个新的域名。依次输入二级域名,一级域名可以选择已经托管到 CloudFlare 上的域名,选择类型 HTTP,URL 地址注意要根据节点类型来确定。vmess 端口:localhost:8001vless 端口:localhost:8002最后点击“Save Hostname”。回到隧道列表,即可看到刚刚生成域名。复制生成的域名。创建 UUID可参见之前的帖子 CloudFlare Workers VLESS 永久免费节点搭建 ,通过在线网站或 V2ray 来生成。至此修改 c.yml 文件所需要的3个信息都已得到。服务器创建节点回到 Slicehosting控制面板 c.yml 文件修改界面,输入上面得到的信息,保存内容。 注意:删除 TOK 和 ARCO_DOMAIN 所在行前的 #,使该行能够生效。TOK 数值只粘贴去除 cloudflared.exe service install 后的字段,包括之间的空格。回到控制台,点击“Start”,服务器会运行上传的文件程序。稍等几分钟后,就会出现节点信息。我们同时也能在隧道列表里看到,刚刚创建的域名也正常运行起来了。但是由于厂商把节点信息给屏蔽掉了,直接是看不到的,需要手动创建节点信息。V2ray 中创建节点手动创建打开 V2ray,添加 VLESS 服务器,按照下图填写对应内容。其中 地址 需要输入优选 IP,不能是上面创建的域名。小程序自动创建节点如果觉得手动输入麻烦,也可以采用下面提供的小工具来自动创建节点。 打开节点信息生成小工具地址:https://github.com/shiteThings/slicehosting ,下载 slicehostinghelper.com.exe 文件。 双击运行该小工具,依次输入 UUID,优选 IP(回车默认设置即可),创建的域名。将生成的 VLESS 节点信息复制粘贴到 V2ray 中即可。优选 IP 工具利用 PingInfoView 这款软件,来优选适合自己的 CloudFlare 优选域名。或者通过优选网站来获取。PingInfoView 优选域名PingInfoView 下载介绍:PingInfoView 是一个小型实用程序,可让您轻松 ping 多个主机名和 IP 地址,并且在一个表格中查看结果。 它每隔指定的秒数自动 ping 到所有主机,并显示成功和失败的 ping 数作为平均 ping 时间。 您也可以将 ping 结果保存到 text/html/xml 文件中,或将其复制到剪贴板。PingInfoView 支持 ICMP ping 和 TCP ping。官网地址:https://www.nirsoft.net/utils/multiple\_ping\_tool.html下载地址:https://www.nirsoft.net/utils/pinginfoview.zip中文汉化包:https://www.nirsoft.net/utils/trans/pinginfoview\_schinese.zip ,将解压的 pinginfoview_lng.ini 文件放在您安装的 PingInfoView 实用程序的同一文件夹中。挑选 CloudFlare 优选域名打开 PingInfoView 程序,在 Ping Options 内粘贴下面的域名。或者选择 从文件载入地址。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 www.sean-now.com download.yunzhongzhuan.com whatismyipaddress.com www.ipaddress.my www.pcmag.com www.ipchicken.com www.iplocation.net iplocation.io www.who.int www.wto.org测下至少 4-5 次,然后点击左上角的 Stop 按钮。按照排序找到 ping 成功率最高的,且延迟最低的域名即可。优选 IP 获取网站获取地址:https://cloudflare.vmshop.org/说明:全自动一小时一更新,无需域名。使用:使用前尽量在浏览器中访问 HTTP,确认可访问 CloudFlare IP。点击“随机优选”,就会出现免费提供的 IP 地址。如果不好,还可以重复选取。文中程序下载c.ymlserver.jarslicehostinghelper.com.exe「利用Slicehosting搭建免费科学上网节点.exe」https://www.alipan.com/s/XXBMn6SLF9J 提取码: Uhks https://www.123pan.com/s/Oy5RVv-y92B.html 提取码:6aBs参考文章https://www.youtube.com/watch?v=\_eo7jYi8UcQhttps://blog.misaka.rest/2023/08/12/pick-cf-best-domain/https://tweek.top/archives/1703662634422【END】
2024年01月13日
24 阅读
4 评论
0 点赞
2024-01-06
自动提取 ChromeGo 一键翻墙包内的免费节点
ChromeGo:Chrome一键翻墙包介绍ChromeGo一个集成 Goflyway、v2ray、Daze、SSR、Brook、Lightsocks、trojan、蓝灯、psiphon 等N多翻墙工具的电脑翻墙包(推荐按前面所列顺序依次尝试),所有工具全部内置免费服务器,长期更新。Github 项目地址:https://github.com/bannedbook/fanqiang由于集成的工具过多,各个节点过于分散,没有做到一个很好的聚合,于是就有了下面介绍的几种提取免费节点的聚会脚本。利用 Python 脚本提取绵羊的金属精炼器参考文档:https://blog.mareep.net/posts/38493/ChromeGo 里面用到的节点池:https://github.com/Alvin9999/pac2/tree/master聚合过程就是提取他各个客户端里面的节点,用了 Python 进行了简单的处理转换,然后放 action 里面每天跑两次。具体操作见 Github 项目地址:https://github.com/vveg26/chromego\_merge演示视频:chromegopacsGithub 项目地址:https://github.com/vveg26/chromego\_merge项目介绍:提取 ChromeGo 经常更新维护的部分节点,显示节点地理位置,6小时运行一次 action 资源。订阅链接:https://raw.githubusercontent.com/markbang/chromegopacs/main/hy2pac.txt利用 Cloudflare Wokers 自动提取Github 项目地址:https://github.com/shiteThings/extractNodesCloudflare 创建 Workers登录 Cloudflare,点击左侧 Workers 和 Pages,点击右侧的 创建应用程序 按钮。点击 创建 Worker 按钮。 命名新的脚本名称,点击 部署 按钮。提示创建成功,点击 编辑代码。删除系统默认代码,复制粘贴下面代码。最后点击 保存与部署。addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { // 定义要发送请求的地址数组,每个元素包含一个 url 和一个处理函数 const sites = [ , , , , , , , , //hy2 , , , , , , , , //xray , , , , , , , //singbox , , , // 添加更多的网站地址和类型... ]; // 存储拼接后的字符串,用于去重 const uniqueStrings = new Set(); // 发送请求并处理响应 async function fetchData(site) { try { const response = await fetch(site.url); const data = await response.json(); // 根据网站类型选择对应的处理函数 let formattedString ; if(site.type === "hysteria") { formattedString = processHysteri(data) } else if(site.type === "hysteria2") { formattedString = processHysteria2(data) } else if(site.type === "xray"){ formattedString = processXray(data) } else { formattedString = processSingbox(data) } // 将拼接的字符串添加到集合中 uniqueStrings.add(formattedString); } catch (error) { console.error(`Error fetching data from $: $`); } } // 处理类型1的数据 function processHysteri(data) { // 从 JSON 数据中提取字段,并按指定格式拼接字符串 const up_mps = data.up_mbps; const down_mps = data.down_mbps; const auth_Str = data.auth_str; const server_name = data.server_name; const alpn = data.alpn; const server = data.server; return `hysteria://$?upmbps=$&downmbps=$&auth=$&insecure=1&peer=$&alpn=$`; } // 处理类型2的数据 function processHysteria2(data) { // 根据类型2的数据提取字段,并按指定格式拼接字符串 // 这里提供一个示例,实际情况需要根据实际数据结构调整 // 从 JSON 数据中提取所需字段 const auth = data.auth || ''; const server = data.server || ''; // 根据 data.insecure 的值设置 insecure const insecure = data.tls.insecure ? 1 : 0; const sni = data.tls.sni || ''; return `hy2://$@$?insecure=$&sni=$`; } // 处理xray的数据 function processXray(data) { let protocol, address, port, id, encryption, type, security, sni, fp, path, host; let outboundConfig = data.outbounds[0]; protocol = outboundConfig.protocol; id = outboundConfig.settings?.vnext?.[0]?.users?.[0]?.id; address = outboundConfig.settings?.vnext?.[0]?.address; port = outboundConfig.settings?.vnext?.[0]?.port; encryption = outboundConfig.settings?.vnext?.[0]?.users?.[0]?.encryption; type = outboundConfig?.streamSettings?.network; security = outboundConfig?.streamSettings?.security; sni = outboundConfig?.streamSettings?.tlsSettings?.serverName; fp = outboundConfig?.streamSettings?.tlsSettings?.fingerprint; path = outboundConfig?.streamSettings?.wsSettings?.path; host = outboundConfig?.streamSettings?.wsSettings?.headers?.Host; return `$://$@$:$?security=$&sni=$&fp=$&type=$&path=$&host=$`; } // 处理singbox的数据 function processSingbox(data) { const outbounds = data.outbounds[0]; const up_mps = outbounds.up_mbps; const down_mps = outbounds.down_mbps; const auth_Str = outbounds.auth_str; const server_name = outbounds.tls.server_name; const alpn = outbounds.tls.alpn[0]; const server = outbounds.server; const port = outbounds.server_port; return `hysteria://$:$?upmbps=$&downmbps=$&auth=$&insecure=1&peer=$&alpn=$`; } // 遍历所有地址并发送请求 const promises = sites.map(site => fetchData(site)); await Promise.all(promises); // 构建最终字符串 const finalStrings = [...uniqueStrings]; // 生成 HTML 页面内容 const htmlContent = finalStrings.map(str => `<p>$</p>`).join('\n'); return new Response(htmlContent, { headers: , }); }查看提取的免费节点接下来就可以通过访问 https://chromego.xxxx.workers.dev/ 的形式,查看自动提取出的免费节点。注意:由于 Cloudflare Workers 被滥用,打开上述链接,有可能需要通过代理(2024-01-10更新)该脚本作者提供了自己维护的提取地址(要开代理才能访问):worker 订阅链接:https://chromegolatest.amazinglinyy.workers.dev/自定义域订阅链接:https://chromenodes.marcol.top/NekoRay - 运行 Hysteria 代理核心参考文档:https://blog.echosec.top/p/nekoray-hysteria/上面提取的节点都是以链接形式呈现的,尤其是 Hysteria 节点,俗称歇斯底里,无法直接复制粘贴到 V2ray 中使用。这里推荐使用 NekoRay 客户端可直接复制粘贴使用提取的免费节点。介绍NekoRay 基于 Qt 的跨平台代理配置管理器 (后端 v2ray / sing-box),目前支持 Windows / Linux 开箱即用。GitHub 项目地址:https://github.com/MatsuriDayo/nekoray项目网址:https://matsuridayo.github.io/项目帮助文档:https://matsuridayo.github.io/index\_docs/使用方法跟 V2ray 类似,支持订阅更新,节点导入支持手动和剪贴板。这里重点介绍一下如何配置使用 Hysteria 协议。下载 Hysteria进入 Hysteria 项目最新下载页面:https://github.com/apernet/hysteria/releases ,下载最新版的 hysteria-windows-amd64.exe,将其放置在 NekoRay 配置目录下,例如:\nekoray\\config\\hysteria-windows-amd64.exe。配置 NekoRay 代理核心这里以最新版 3.26 版本为例:打开 NekoRay,进入「首选项」->「基本设置」->「其他核心」界面,选择 hysteria 的绝对路径,即刚下载的 hysteria-windows-amd64.exe 文件绝对路径。导入启动节点接下来从剪贴板导入节点,测试没问题后,启动节点,即可使用提取的免费节点进行科学上网了。设置绕过局域网和大陆的路由规则依次点击“首选项”→“路由设置”,转到“简易路由”页,点击“预设”里的“绕过局域网和大陆”,然后点击“确定”即可。【END】
2024年01月06日
145 阅读
0 评论
0 点赞
2023-08-06
CloudFlare Workers VLESS 永久免费节点搭建
前言近期很火的利用 CloudFlare Workers 搭建 VLESS 节点,支持自定义伪装网页,可以 CDN 自选域名,13个端口随便换。注册 CloudFlare相信很多人都有 CloudFlare 帐户,这里额外介绍一个临时邮箱地址,用于注册 CloudFlare。 临时邮箱:https://www.linshiyouxiang.net/CloudFlare Workers vless 代码部署登录 CloudFlare:https://dash.cloudflare.com/点击 Workers 和 Pages,点击 创建 Workers。 创建一个脚本名称,比如 vless,点击 部署。 点击 配置 Worker,点击 快速编辑。清除默认代码。将下面章节提到的代码,粘贴到代码框中。找到 let userID = 和 let proxyIP,更改 UUID 和推荐的 CDN 加速 IP。点击 保存并部署。以下介绍两组代码供选择使用,强烈建议更换默认的 userID,如果不更换,容易被扫描盗用。生成 UUID在线生成地址:https://1024tools.com/uuid打开 V2ray 客户端,选择 添加 VLESS 服务器,在弹出的对话框中,点击 生成 按钮,即可生成 UUID。CDN 加速 IPcdn-all.xn--b6gac.eu.org cdn.xn--b6gac.eu.org cdn-b100.xn--b6gac.eu.org edgetunnel.anycast.eu.org cdn.anycast.eu.org(亚洲地区) jp.cloudflarest.link achk.cloudflarest.link(阿里香港优选)vless 部署代码1代码引用地址:https://github.com/zizifn/edgetunnel/blob/main/src/worker-vless.js 注意:需要更改 UUID, 添加 proxyIP 地址。// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->. // @ts-ignore import 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 request * @param } env * @param ctx * @returns */ 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), ); case `/$`: { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response(`$`, { status: 200, headers: { "Content-Type": "text/plain;charset=utf-8", } }); } default: return new Response('Not found', ); } } else { return await vlessOverWSHandler(request); } } catch (err) { /** @type */ let e = err; return new Response(e.toString()); } }, }; /** * * @param request */ async function vlessOverWSHandler(request) { /** @type */ // @ts-ignore const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); let address = ''; let portWithRandomLog = ''; const log = (/** @type */ info, /** @type */ event) => { console.log(`[$:$] $`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); /** @type }*/ 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 = `$--$ ${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 = 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 remoteSocket * @param addressRemote The remote address to connect to. * @param portRemote The remote port to connect to. * @param rawClientData The raw client data to write. * @param webSocket The WebSocket to pass the remote socket to. * @param vlessResponseHeader The VLESS response header. * @param log The logging function. * @returns The remote socket. */ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { async function connectAndWrite(address, port) { /** @type */ const tcpSocket = connect({ hostname: address, port: port, }); remoteSocket.value = tcpSocket; log(`connected to $:$`); 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 webSocketServer * @param earlyDataHeader for ws 0rtt * @param 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 = 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 $`) 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 vlessBuffer * @param 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 $ 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 $`, }; } if (!addressValue) { return { hasError: true, message: `addressValue is empty, addressType is $`, }; } return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, }; } /** * * @param remoteSocket * @param webSocket * @param vlessResponseHeader * @param retry * @param log */ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // check if remoteSocket has incoming data await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param 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 $`); // 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 base64Str * @returns */ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return ; } 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 ; } catch (error) { return ; } } /** * This is not real UUID validation * @param uuid */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]-[0-9a-f]-[4][0-9a-f]-[89ab][0-9a-f]-[0-9a-f]$/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 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 webSocket * @param vlessResponseHeader * @param 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 $`); 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 chunk */ write(chunk) { writer.write(chunk); } }; } /** * * @param userID * @param hostName * @returns */ function getVLESSConfig(userID, hostName) { const vlessMain = `vless://$@$:443?encryption=none&security=tls&sni=$&fp=randomized&type=ws&host=$&path=%2F%3Fed%3D2048#$` return ` ################################################################ v2ray --------------------------------------------------------------- $ --------------------------------------------------------------- ################################################################ clash-meta --------------------------------------------------------------- - type: vless name: $ server: $ port: 443 uuid: $ network: ws tls: true udp: false sni: $ client-fingerprint: chrome ws-opts: path: "/?ed=2048" headers: host: $ --------------------------------------------------------------- ################################################################ `; }vless 部署代码2代码引用地址:https://github.com/yonggekkk/CF-workers-pages-vless/blob/main/\_worker.js需要更改 UUID自带 CloudFlare 证书的 proxyIP 地址代码104行,支持自定义伪装网页,修改对应的网页地址即可。代码中地址为 CCTV 官网// <!--GAMFC-->version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC<!--GAMFC-END-->. // @ts-ignore import 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'; const proxyIPs = ['cdn-all.xn--b6gac.eu.org', 'cdn-all.xijingping.link', 'cdn.xn--b6gac.eu.org', 'cdn-b100.xn--b6gac.eu.org', 'edgetunnel.anycast.eu.org', 'cdn.anycast.eu.org']; let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)]; let dohURL = 'https://sky.rethinkdns.com/1:-Pf_____9_8A_AMAIgE8kMABVDDmKOHTAKg='; // https://cloudflare-dns.com/dns-query or https://dns.google/dns-query // v2board api environment variables let nodeId = ''; // 1 let apiToken = ''; //abcdefghijklmnopqrstuvwxyz123456 let apiHost = ''; // api.v2board.com if (!isValidUUID(userID)) { throw new Error('uuid is not valid'); } export default { /** * @param request * @param } env * @param ctx * @returns */ async fetch(request, env, ctx) { try { userID = env.UUID || userID; proxyIP = env.PROXYIP || proxyIP; dohURL = env.DNS_RESOLVER_URL || dohURL; nodeId = env.NODE_ID || nodeId; apiToken = env.API_TOKEN || apiToken; apiHost = env.API_HOST || apiHost; const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { const url = new URL(request.url); switch (url.pathname) { case '/cf': return new Response(JSON.stringify(request.cf, null, 4), { status: 200, headers: { "Content-Type": "application/json;charset=utf-8", }, }); case '/connect': // for test connect to cf socket const [hostname, port] = ['cloudflare.com', '80']; console.log(`Connecting to $:$...`); try { const socket = await connect({ hostname: hostname, port: parseInt(port, 10), }); const writer = socket.writable.getWriter(); try { await writer.write(new TextEncoder().encode('GET / HTTP/1.1\r\nHost: ' + hostname + '\r\n\r\n')); } catch (writeError) { writer.releaseLock(); await socket.close(); return new Response(writeError.message, ); } writer.releaseLock(); const reader = socket.readable.getReader(); let value; try { const result = await reader.read(); value = result.value; } catch (readError) { await reader.releaseLock(); await socket.close(); return new Response(readError.message, ); } await reader.releaseLock(); await socket.close(); return new Response(new TextDecoder().decode(value), ); } catch (connectError) { return new Response(connectError.message, ); } case `/$`: { const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); return new Response(`$`, { status: 200, headers: { "Content-Type": "text/plain;charset=utf-8", } }); } default: // return new Response('Not found', ); // For any other path, reverse proxy to 'www.fmprc.gov.cn' and return the original response url.hostname = 'tv.cctv.com'; url.protocol = 'https:'; request = new Request(url, request); return await fetch(request); } } else { return await vlessOverWSHandler(request); } } catch (err) { /** @type */ let e = err; return new Response(e.toString()); } }, }; /** * * @param request */ async function vlessOverWSHandler(request) { /** @type */ // @ts-ignore const webSocketPair = new WebSocketPair(); const [client, webSocket] = Object.values(webSocketPair); webSocket.accept(); let address = ''; let portWithRandomLog = ''; const log = (/** @type */ info, /** @type */ event) => { console.log(`[$:$] $`, event || ''); }; const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); /** @type }*/ 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, } = await processVlessHeader(chunk, userID); address = addressRemote; portWithRandomLog = `$--$ ${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 = 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, }); } let apiResponseCache = null; let cacheTimeout = null; /** * Fetches the API response from the server and caches it for future use. * @returns A Promise that resolves to the API response object or null if there was an error. */ async function fetchApiResponse() { const requestOptions = { method: 'GET', redirect: 'follow' }; try { const response = await fetch(`https://$/api/v1/server/UniProxy/user?node_id=$&node_type=v2ray&token=$`, requestOptions); if (!response.ok) { console.error('Error: Network response was not ok'); return null; } const apiResponse = await response.json(); apiResponseCache = apiResponse; // Refresh the cache every 5 minutes (300000 milliseconds) if (cacheTimeout) { clearTimeout(cacheTimeout); } cacheTimeout = setTimeout(() => fetchApiResponse(), 300000); return apiResponse; } catch (error) { console.error('Error:', error); return null; } } /** * Returns the cached API response if it exists, otherwise fetches the API response from the server and caches it for future use. * @returns A Promise that resolves to the cached API response object or the fetched API response object, or null if there was an error. */ async function getApiResponse() { if (!apiResponseCache) { return await fetchApiResponse(); } return apiResponseCache; } /** * Checks if a given UUID is present in the API response. * @param targetUuid The UUID to search for. * @returns A Promise that resolves to true if the UUID is present in the API response, false otherwise. */ async function checkUuidInApiResponse(targetUuid) { // Check if any of the environment variables are empty if (!nodeId || !apiToken || !apiHost) { return false; } try { const apiResponse = await getApiResponse(); if (!apiResponse) { return false; } const isUuidInResponse = apiResponse.users.some(user => user.uuid === targetUuid); return isUuidInResponse; } catch (error) { console.error('Error:', error); return false; } } // Usage example: // const targetUuid = "65590e04-a94c-4c59-a1f2-571bce925aad"; // checkUuidInApiResponse(targetUuid).then(result => console.log(result)); /** * Handles outbound TCP connections. * * @param remoteSocket * @param addressRemote The remote address to connect to. * @param portRemote The remote port to connect to. * @param rawClientData The raw client data to write. * @param webSocket The WebSocket to pass the remote socket to. * @param vlessResponseHeader The VLESS response header. * @param log The logging function. * @returns The remote socket. */ async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { async function connectAndWrite(address, port) { /** @type */ const tcpSocket = connect({ hostname: address, port: port, }); remoteSocket.value = tcpSocket; log(`connected to $:$`); 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 webSocketServer * @param earlyDataHeader for ws 0rtt * @param 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 = 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 $`) 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 vlessBuffer * @param userID * @returns */ async 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; const slicedBuffer = new Uint8Array(vlessBuffer.slice(1, 17)); const slicedBufferString = stringify(slicedBuffer); const uuids = userID.includes(',') ? userID.split(",") : [userID]; const checkUuidInApi = await checkUuidInApiResponse(slicedBufferString); isValidUser = uuids.some(userUuid => checkUuidInApi || slicedBufferString === userUuid.trim()); console.log(`checkUuidInApi: $, userID: $`); 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 $ 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 $`, }; } if (!addressValue) { return { hasError: true, message: `addressValue is empty, addressType is $`, }; } return { hasError: false, addressRemote: addressValue, addressType, portRemote, rawDataIndex: addressValueIndex + addressLength, vlessVersion: version, isUDP, }; } /** * * @param remoteSocket * @param webSocket * @param vlessResponseHeader * @param retry * @param log */ async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { // remote--> ws let remoteChunkCount = 0; let chunks = []; /** @type */ let vlessHeader = vlessResponseHeader; let hasIncomingData = false; // check if remoteSocket has incoming data await remoteSocket.readable .pipeTo( new WritableStream({ start() { }, /** * * @param 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 $`); // 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 base64Str * @returns */ function base64ToArrayBuffer(base64Str) { if (!base64Str) { return ; } 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 ; } catch (error) { return ; } } /** * This is not real UUID validation * @param uuid */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]-[0-9a-f]-[4][0-9a-f]-[89ab][0-9a-f]-[0-9a-f]$/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 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 webSocket * @param vlessResponseHeader * @param 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(dohURL, // dns server url { 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 $`); 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 chunk */ write(chunk) { writer.write(chunk); } }; } /** * * @param userID * @param hostName * @returns */ function getVLESSConfig(userID, hostName) { const vlessws = `vless://$@time.cloudflare.com:8880?encryption=none&type=ws&host=$&path=%2F%3Fed%3D2048#$` const vlesswstls = `vless://$@time.cloudflare.com:8443?encryption=none&security=tls&type=ws&host=兄弟,你的自定义域名呢?&path=%2F%3Fed%3D2048#$` return ` ==========================配置详解============================== ################################################################ 一、CF-workers-vless+ws节点,分享链接如下: $ --------------------------------------------------------------- 注意:当前节点无需域名,TLS选项关闭 --------------------------------------------------------------- 客户端必要文明参数如下: 客户端地址(address):自选域名 或者 自选IP 端口(port):7个http端口可任意选择(80、8080、8880、2052、2082、2086、2095) 用户ID(uuid):$ 传输协议(network):ws/websocket 伪装域名(host):$ 路径(path):/?ed=2048 ################################################################ ################################################################ 二、CF-workers-vless+ws+tls节点,分享链接如下: $ --------------------------------------------------------------- 注意:客户端ws选项后的伪装域名host必须改为你自定义的域名 --------------------------------------------------------------- 客户端必要文明参数如下: 客户端地址(address):自选域名 或者 自选IP 端口(port):6个https端口可任意选择(443、8443、2053、2083、2087、2096) 用户ID(uuid):$ 传输协议(network):ws/websocket 伪装域名(host):兄弟,你的自定义域名呢? 路径(path):/?ed=2048 传输安全(TLS):开启 跳过证书验证(allowlnsecure):false ################################################################ ################################################################ clash-meta --------------------------------------------------------------- - type: vless name: $ server: $ port: 443 uuid: $ network: ws tls: true udp: false sni: $ client-fingerprint: chrome ws-opts: path: "/?ed=2048" headers: host: $ --------------------------------------------------------------- ################################################################ `; }V2ray 使用 CloudFlare Workers vless 节点(无域名)复制刚刚创建的代码地址,格式为:https://vless.xxx.workers.dev/,其中 vless 为创建的脚本名称,xxx 为 CloudFlare 用户名。在浏览器地址栏中粘贴代码地址后,如果出现一段字符,即表明创建成功。接下来在该地址后输入 /UUID字符串,回车即可看到 vless 地址。 将页面中的 V2ray 地址复制,打开 V2ray 软件,点击 服务器 选项,选择 从剪贴板导入批量URL,即可看到创建的节点。双击刚刚创建的节点,将端口(port)改为 2052,传输层安全(TLS) 设置为空,传输协议(network) 为 ws,地址(address) 改为优选 IP。 注:7个 http 端口可任意选择:80、8080、8880、2052、2082、2086、2095 Workers 专用 IP 优选工具CloudFlare 优质 IP 自动切换:https://stock.hostmonit.com/CloudFlareYes172.64.32.1/24 (推荐移动,走香港) 104.28.14.0/24 (推荐移动,走新加坡) 104.23.240.0 ~ 104.23.243.254 (推荐联通、移动,线路未知) 108.162.236.1/24 (推荐联通,走美国) 104.20.157.0/24 (推荐联通,走日本) 104.16.160.1/24 (推荐电信,走洛杉矶) 172.64.0.0/24 (推荐电信,走旧金山) 172.64.32.* (走欧洲)优选工具 Windows 版本下载地址,断开所有 VPN 和科学上网方式,选择运行任意批处理文件,在运行后的结果中,选择 PING 值低的 IP 地址,复制到 V2ray 的 地址(address) 中。 CloudFlare 自选域名自选域名下载地址,断开所有 VPN 和科学上网方式,选择自选域名程序单文件,运行后在相同文件夹下中打开 txt 文件,选择 PING 值低的域名,复制到 V2ray 的 地址(address) 中。V2ray 使用 CloudFlare Workers vless 节点(有域名)在 CloudFlare Workers 中,打开刚才创建的脚本,点击 查看,点击 添加自定义域。 在自定义域对话框中输入已经在 CloudFlare 注册域名的二级域名,点击 添加自定义域。比如:vless.5iehome.cf。接下来只需在浏览器地址栏中粘贴输入 https://vless.5iehome.cf/UUID字符串,回车即可看到 vless 地址。将页面中的 V2ray 地址复制,打开 V2ray 软件,点击 服务器 选项,选择 从剪贴板导入批量URL,即可看到创建的节点。双击刚刚创建的节点,将端口(port)改为 443,传输层安全(TLS) 设置为 tls,传输协议(network) 为 ws,地址(address) 改为优选 IP。 注:6个 http 端口可任意选择:443、8443、2053、2083、2087、2096 节点测速V2ray 下测试节点速度,要选择 测试服务器真连接延迟(多选)(Ctrl+R),延迟数值为大于 0 即为导通,数值为 -1 即为不导通。IP 地址查看https://whatismyipaddress.com/https://ip.gs文中程序下载CloudFlare Workers VLESS 永久免费节点搭建https://www.aliyundrive.com/s/mXFeMtdBF2T 提取码: c1m7https://www.123pan.com/s/Oy5RVv-swXB.html 提取码: V3iX参考文章https://jdssl.top/index.php/2023/07/21/2023vpn/https://www.youtube.com/watch?v=5fvhws6ZXrMhttps://ygkkk.blogspot.com/2023/07/cfworkers-vless.htmlhttps://www.youtube.com/watch?v=9V9CQxmfwoA【END】
2023年08月06日
27 阅读
3 评论
0 点赞
2023-08-06
CloudFlare WARP 免费 VPN 搭建教程
前言最近比较火的科学上网的方法,是使用 CloudFlare WARP 免费 VPN,并且可以优选 IP,提高上网速度。CloudFlare WARP 免费 VPNWARP 介绍1.1.1.1 的 WARP 应用以一个经过优化的现代协议来代替您的设备和 Internet 之间的连接。具有快速、免费、私密的特点,最关键的通过优选 IP 后,可以大幅度提高上网速度。下载安装WARP 支持各个平台,Windows 的下载地址为:Microsoft应用中心1.1.1.1下载后双击安装即可。直接使用 WARP在状态栏打开软件,点击连接按钮即可使用。默认情况下,软件会自动分配一个普通账号,只有 1GB 的使用流量。获取 WARP+ 永久免费流量打开 Telegram,搜索 @generatewarpplusbot按照提示,关联相关订阅后,获取 WARP 账户许可证密钥 (26个字符),格式为:xxxxxxxx-xxxxxxxx-xxxxxxxx使用 WARP+ 免费流量方法一:更改 WARP 帐户直接在 WARP 中选择 帐户,点击 使用其他密钥 按钮,在弹出的对话框中输入在 Telegram 上得到的许可证密钥。方法二:使用 WireGuard下载 WireGuard,下载对应客户端。经测试,下载及安装都需要代理才能完成。在线生成 WARP 的 WireGuard 配置文件,打开生成配置文件网址,点击 Run,在运行代码框中输入 2,即 WARP+。然后输入在 Telegram 上得到的26位许可证密钥。设备名设置可有可无。 将生成的配置文件输入到 WireGuard 中,就可以连接使用了。 WireGuard 可以脱离 CloudFlare WARP 独立使用。CloudFlare 优化 IPWARP+ IP 优选工具下载:https://gitlab.com/Misaka-blog/warp-script/-/blob/main/files/warp-yxip/warp-yxip-win.7z断开所有 VPN 和科学上网方式,双击 warp-yxip.bat,选择默认 1 IPv4 优选地址即可。打开 result.csv,选择延迟小,丢包率为 0 的 IP 地址即可。 复制延迟小,丢包率为 0 的 IP 地址及端口号,将 WireGuard 配置文件 Endpoint 地址替换,保存后再连接,就会发现速度有了明显改善。 文中软件下载地址「CloudFlare WARP.exe」https://www.123pan.com/s/Oy5RVv-wwXB.html 提取码: 7iAahttps://www.aliyundrive.com/s/Rn7dPV86Nqn 提取码: 6ep3【END】
2023年08月06日
55 阅读
0 评论
0 点赞
2023-04-22
CloudFlare Tunnel 免费内网穿透的简明教程
本文作者:Antiscientist 转载地址:https://sspai.com/post/79278简介CloudFlare Tunnel 免费内网穿透的简明教程Tunnel可以做什么将本地网络的服务暴露到公网,可以理解为内网穿透。 例如我们在本地服务器 192.168.1.1:3000 搭建了一个 Transmission 服务用于 BT 下载,我们只能在内网环境才能访问这个服务,但通过内网穿透技术,我们可以在任何广域网环境下访问该服务。相比 NPS 之类传统穿透服务,Tunnel 不需要公网云服务器,同时自带域名解析,无需 DDNS 和公网 IP。将非常规端口服务转发到 80/443 常规端口。 无论是使用公网 IP + DDNS 还是传统内网穿透服务,都免不了使用非常规端口进行访问,如果某些服务使用了复杂的重定向可能会导致 URL 中端口号丢失而引起不可控的问题,同时也不够优雅。自动为你的域名提供 HTTPS 认证。为你的服务提供额外保护认证。最重要的是——免费。Tunnel 工作原理Tunnel 通过在本地网络运行的一个 Cloudflare 守护程序,与 Cloudflare 云端通信,将云端请求数据转发到本地网络的 IP + 端口。前置条件持有一个域名将域名 DNS 解析托管到 CF内网有一台本地服务器,用于运行本地与 cloudflare 通信的 cloudflared 程序一张境内双币信用卡(仅用于添加付款方式,服务是免费的)开始1. 打开 Cloudflare Zero Trust 工作台面板2. 创建 Cloudflare Zero Trust ,选择免费计划。需要提供付款方式,使用境内的双币卡即可填写team name,随意填写选择免费计划添加付款方式填写信用卡信息(仅验证,不会扣款),完成配置3. 完成后,在 Access Tunnels 中,创建一个 Tunnel。创建 Tunnel4. 选择 Cloudflared 部署方式。Tunnel 需要通过 Cloudflared 来建立云端与本地网络的通道,这里推荐选择 Docker 部署Cloudflared 守护进程以使用 Tunnel 功能。获取 Cloudflared 启动命令及 Token点击复制按钮复制指令,在本地网络主机上运行命令。我们还可以加上--name cloudflared -d --restart unless-stop为 Docker 容器增加名称和后台运行。你可以使用下方我修改好命令来创建 Docker,注意替换你为自己的 Token(就是网页中 —-token 之后的长串字符)docker run --name cloudflared -d --restart unless-stop cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <YourToken>5. 配置域名和转发URL为你的域名配置一个子域名(Subdomain),Path 留空,URL 处填写内网服务的IP加端口号。注意 Type 处建议使用 HTTP,因为 Cloudflare 会自动为你提供 HTTPS,因此此处的转发目标可以是 HTTP 服务端口。配置内网目标 IP+端口完成接着访问刚刚配置的三级域名,例如 https://app.yourdomain.com(是的,你没看错,是 https,cloudflare 已经自动为域名提供了 https 证书)就可以访问到内网的非公端口号服务了。一个 Tunnel 中可以添加多条三级域名来跳转到不同的内网服务,在 Tunnel 页面的 Public Hostname 中新增即可。为你的服务添加额外验证如果你觉得这种直接暴露内网服务的方式有较高的安全风险,我们还可以使用 Application 功能为服务添加额外的安全验证。1. 点击 Application - Get started。创建 Application2. 选择 Self-hosted。选择类型3. 填写配置,注意 Subdomain 和 Domain 需要使用刚刚创建的 Tunnel 服务相同的 Domain 配置。配置三级域名4. 选择验证方式。填写 Policy name(任意)。在 Include 区域选择验证方式,示例图片中使用的是 Email 域名的方式,用户在访问该网络时需要使用指定的邮箱域名(如@gmail.com)验证,这种方式比较适合自定义域名的企业邮箱用户。另外你还可以指定特定完整邮箱地址、IP 地址范围等方式。选择验证方式5. 完成添加此时,访问 https://app.yourdomain.com 可以看到网站多了一个验证页面,使用刚刚设置的域名邮箱,接收验证码来访问。评价除了上述直接转发 http 服务之外,Tunnel 还支持 RDP、SSH 等协议的转发,玩法丰富,有待各位探索。作为一款免费的服务,简单的配置,低门槛使用条件,适合各位 Self-hosted 玩家尝试。不过要注意的是 Tunnel 在国内访问速度不快,并且有断流的情况,请酌情使用。【END】
2023年04月22日
8 阅读
0 评论
0 点赞
1
2