Giter Club home page Giter Club logo

edtunnel's Introduction

EDtunnel

edgetunnel

GitHub Repository for https://github.com/zizifn/edgetunnel

ask question and cloudflare ips: https://t.me/edtunnel

Repository

available branches and explain

Branch Name Description
remote-socks5 Branch for remote SOCKS5 proxy pool used implementation
socks5 Branch for SOCKS5 proxyIP implementation
vless Branch for outbound VLESS protocol implementation
vless2 Branch for alternative outbound VLESS protocol implementation
code1 Branch for code1 feature development
code2 Branch for code2 alternative feature development
dns Branch for DNS alternative related development
main Main branch for the project
pages New version for deployment on Cloudflare Pages

Deploy in pages.dev

  1. See YouTube Video:

    https://www.youtube.com/watch?v=8I-yTNHB0aw

  2. Clone this repository deploy in cloudflare pages.

Deploy in worker.dev

  1. Copy _worker.js code from here.

  2. Alternatively, you can click the button below to deploy directly.

    Deploy to Cloudflare Workers

Lazy to deploy

aHR0cHM6Ly9vc3MudjJyYXlzZS5jb20vcHJveGllcy9kYXRhLzIwMjMtMDctMzAvRnJFS1lvQS50eHQ= (free clash.meta subscribe config)

UUID Setting (Optional)

  1. When deploy in cloudflare pages, you can set uuid in wrangler.toml file. variable name is UUID. wrangler.toml file is also supported. (recommended) in case deploy in webpages, you can not set uuid in wrangler.toml file.

  2. When deploy in worker.dev, you can set uuid in _worker.js file. variable name is userID. wrangler.toml file is also supported. (recommended) in case deploy in webpages, you can not set uuid in wrangler.toml file. in this case, you can also set uuid in UUID enviroment variable.

Note: UUID is the uuid you want to set. pages.dev and worker.dev all of them method supported, but depend on your deploy method.

UUID Setting Example

  1. single uuid environment variable

    UUID = "uuid here your want to set"
    
  2. multiple uuid environment variable

    UUID = "uuid1,uuid2,uuid3"
    

    note: uuid1, uuid2, uuid3 are separated by commas,. when you set multiple uuid, you can use https://edtunnel.pages.dev/uuid1 to get the clash config and vless:// link.

subscribe vless:// link (Optional)

  1. visit https://edtunnel.pages.dev/uuid your set to get the subscribe link.

  2. visit https://edtunnel.pages.dev/sub/uuid your set to get the subscribe content with uuid your set path.

    note: uuid your set is the uuid you set in UUID enviroment or wrangler.toml, _worker.js file. when you set multiple uuid, you can use https://edtunnel.pages.dev/sub/uuid1 to get the subscribe content with uuid1 path.(only support first uuid in multiple uuid set)

  3. visit https://edtunnel.pages.dev/sub/uuid your set/?format=clash to get the subscribe content with uuid your set path and clash format. content will return with base64 encode.

    note: uuid your set is the uuid you set in UUID enviroment or wrangler.toml, _worker.js file. when you set multiple uuid, you can will use https://edtunnel.pages.dev/sub/uuid1/?format=clash to get the subscribe content with uuid1 path and clash format.(only support first uuid in multiple uuid set)

subscribe Cloudflare bestip(pure ip) link

  1. visit https://edtunnel.pages.dev/bestip/uuid your set to get subscribe info.

  2. cpoy subscribe url link https://edtunnel.pages.dev/bestip/uuid your set to any clients(clash/v2rayN/v2rayNG) you want to use.

  3. done. if have any questions please join @edtunnel

multiple port support (Optional)

For a list of Cloudflare supported ports, please refer to the official documentation.

By default, the port is 80 and 443. If you want to add more ports, you can use the following ports:

80, 8080, 8880, 2052, 2086, 2095, 443, 8443, 2053, 2096, 2087, 2083
http port: 80, 8080, 8880, 2052, 2086, 2095
https port: 443, 8443, 2053, 2096, 2087, 2083

if you deploy in cloudflare pages, https port is not supported. Simply add multiple ports node drictly use subscribe link, subscribe content will return all Cloudflare supported ports.

proxyIP (Optional)

  1. When deploy in cloudflare pages, you can set proxyIP in wrangler.toml file. variable name is PROXYIP.

  2. When deploy in worker.dev, you can set proxyIP in _worker.js file. variable name is proxyIP.

note: proxyIP is the ip or domain you want to set. this means that the proxyIP is used to route traffic through a proxy rather than directly to a website that is using Cloudflare's (CDN). if you don't set this variable, connection to the Cloudflare IP will be cancelled (or blocked)...

resons: Outbound TCP sockets to Cloudflare IP ranges are temporarily blocked, please refer to the tcp-sockets documentation

Usage

frist, open your pages.dev domain https://edtunnel.pages.dev/ in your browser, then you can see the following page: The path /uuid your seetting to get the clash config and vless:// link.

Star History

Star History Chart

edtunnel's People

Contributors

3kmfi6hp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

edtunnel's Issues

使用此脚本可以优选IP的端口有哪些?

大佬我看到你943行的let portArray_https = [443, 8443, 2053, 2096, 2087, 2083];这个意思是我在优选IP的时候这些端口都可以去测试优选而不局限在443端口吗?还是说236行的portRemote = 443,决定了我只能优选443端口?如果都可以的话我把不同端口优选出来的IP全放在10行const proxyIPs里就可以吗?

error

Uncaught Error: uuid is invalid at worker.js:18:8 (Code: 10021)

无法播放youtube

谢谢无私分享,网速非常好,不知道为啥v2rayng连接后不能看油管

在OpenWRT上的Passwall上使用时,TV版安卓和NS不能上网,但手机、平板和电脑能正常使用的问题

作者你好, 我在OpenWRT上的Passwall上使用CloudFlare 取得的Vless节点链接时,出现了TV 安卓系统和NSwitch不能连接上网的问题,但手机、电脑能正常上网,看上去好像只对安卓TV系统限制了,但使用其它的免费节点是没有发生这种情况的。现在我的智能电视与机顶盒都不能直接通过这种CloudFlare 取得的Vless节点上网,必须通过机顶盒安装V2rayNG来实现,希望作者能解决一下这方面的小问题。

IP changes too frequently

Is there any way to make the connection last longer so it doesn't have to come up with new IPs all the time? The ip changes multiple times in just a few seconds and that's the reason some websites have trouble loading anything.

I know that we can't have control over what IP we get, but it could be great if each connection could last longer.

无法上ChatGPT的网站chat.openai.com但是其它谷歌什么的没有什么问题

我按照教程搭建了workers应用,用来上YouTube谷歌搜索什么的,都没有问题。但是唯独无法上ChatGPT的网站chat.openai.com。每次用ChatGPT,我都需要切换到别的代理(网上免费的节点,ChatGPT可以用,但是看油管什么的很慢)。所以现在我只能不用chatgpt的时候用worker代理,要上chatgpt的时候切换到一般节点,使用体验非常繁琐。

请问大佬,无法上ChatGPT这是为什么?有解决方案吗?

Network Error when downloading large files from huggingface

When I download large files from hugging, it always fails, reporting a network error.

The files are rather large, probably a couple G's, and when I download them, they fail after about a couple seconds.

Do you know what is the reason for this, please? Is it because of my settings or is it a limitation of cloudflare itself.

ChatGPT

我做好了workers,访问网站正常,但是无法访问chatgpt,有什么办法吗?
QQ截图20230913110056

慎用,可能会被白嫖、泄漏真实IP并被公开

大概看了下代码,有以下风险,看起来是故意的,不知道是钓鱼还是为了牟利,谨慎使用!!

  1. 收集当前配置信息,用于白嫖或分发进行收费牟利
  2. 会请求政府网站,泄漏真实 IP
  3. 包含 VLESS 相关信息,会被各类爬虫和搜索引擎扫描收录
  • 75 行 会上传当前的地址和配置信息

const bestiplink = `https://sub.xf.free.hr/auto?host=${request.headers.get('Host')}&uuid=${userID_Path}`

const bestiplink = `https://sub.xf.free.hr/auto?host=${request.headers.get('Host')}&uuid=${userID_Path}`
  • 84行 会请求政府网站,并携带真实 IP

newHeaders.set('cf-connecting-ip', newHeaders.get('x-forwarded-for') || newHeaders.get('cf-connecting-ip'));

const hostnames = ['www.fmprc.gov.cn', 'www.xuexi.cn', 'www.gov.cn', 'mail.gov.cn', 'www.mofcom.gov.cn', 'www.gfbzb.gov.cn', 'www.miit.gov.cn', 'www.12377.cn'];
url.hostname = hostnames[Math.floor(Math.random() * hostnames.length)];
url.protocol = 'https:';

newHeaders.set('cf-connecting-ip', newHeaders.get('x-forwarded-for') || newHeaders.get('cf-connecting-ip'));
newHeaders.set('x-forwarded-for', newHeaders.get('cf-connecting-ip'));
newHeaders.set('x-real-ip', newHeaders.get('cf-connecting-ip'));
newHeaders.set('referer', 'https://www.google.com/q=edtunnel');
  • 728 行 会上传当前的地址和配置信息,并且包含 VLESS 相关信息,会被各类爬虫和搜索引擎扫描

const clash_link = `https://subconverter.do.xn--b6gac.eu.org/sub?target=clash&url=https://${hostName}/sub/${userIDArray[0]}?format=clash&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;

const clash_link = `https://subconverter.do.xn--b6gac.eu.org/sub?target=clash&url=https://${hostName}/sub/${userIDArray[0]}?format=clash&insert=false&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
header.push(`\n<p align="center"><img src="https://cloudflare-ipfs.com/ipfs/bafybeigd6i5aavwpr6wvnwuyayklq3omonggta4x2q7kpmgafj357nkcky" alt="图片描述" style="margin-bottom: -50px;">`);
header.push(`\n<b style=" font-size: 15px;" >Welcome! This function generates configuration for VLESS protocol. If you found this useful, please check our GitHub project for more:</b>\n`);
header.push(`<b style=" font-size: 15px;" >欢迎!这是生成 VLESS 协议的配置。如果您发现这个项目很好用,请查看我们的 GitHub 项目给我一个star:</b>\n`);
header.push(`\n<a href="https://github.com/3Kmfi6HP/EDtunnel" target="_blank">EDtunnel - https://github.com/3Kmfi6HP/EDtunnel</a>\n`);
header.push(`\n<iframe src="https://ghbtns.com/github-btn.html?user=USERNAME&repo=REPOSITORY&type=star&count=true&size=large" frameborder="0" scrolling="0" width="170" height="30" title="GitHub"></iframe>\n\n`.replace(/USERNAME/g, "3Kmfi6HP").replace(/REPOSITORY/g, "EDtunnel"));
header.push(`<a href="//${hostName}/sub/${userIDArray[0]}" target="_blank">VLESS 节点订阅连接</a>\n<a href="clash://install-config?url=${encodeURIComponent(clash_link)}" target="_blank">Clash 节点订阅连接</a>\n<a href="${clash_link}" target="_blank">Clash 节点订阅连接2</a></p>\n`);
header.push(``);
  • 751行 包含 VLESS 相关信息,会被各类爬虫和搜索引擎扫描

<meta name="description" content="This is a tool for generating VLESS protocol configurations. Give us a star on GitHub https://github.com/3Kmfi6HP/EDtunnel if you found it useful!">

<meta name="description" content="This is a tool for generating VLESS protocol configurations. Give us a star on GitHub https://github.com/3Kmfi6HP/EDtunnel if you found it useful!">

Issue

Hi.
I have a question for you
I ran your project on CF worker and got my subscription link and got my configs.
But the problem is i can only use Telegram and the web i could not use instagram and twitter!
Can you help me with this ?!

作者太棒了把,写的太好了

Git clone error

If you use "git clone" in the terminal, you get an error.

image

openwrt上passwall2与clash都尝试过,可以打开油管与google,但cloudflare进不去了

网络环境:PVE虚拟机装了openwrt与ROS,openwrt做旁路由,ROS做主路由
操作:分别将“Vless订阅连接”与“clash订阅连接2”,导入passwall2与openclash,
现象:油管与google都可以顺利打开,但cloudflare进不去,而且与cloudflare相关的网站都打不开了,包括我自己的二级域名,whoer.net等网站。单开passwall2与openclash,情况一样。
另外:用了新脚本后,openclash中的“IP.SB 国外"一直在查询中;ip111.cn中”从谷歌测试”显示不了。
排除操作:更换了其他版本的,没有出现上述问题(只限于导入passwall2的Vless订阅连接,以前版本的clash的订阅连接下载不能成功)
其他人遇到过这样的问题吗?

Proxyip

How can I turn my own server to a proxyip to use in this script.
Can you please explain it completely?

路径问题

不在works里配置proxy ip的情况下 在客户端路径里填写/?ed=2048&proxy=1.1.1.1(反代cfiip)是否生效

[Feature request] 定时更新检查反代IP的失效

描述

作者您好。

我在简单的搜索了 proxyIP 字段后,发现该变量似乎在worker运行后就会固定。

const proxyIPs = ['cdn-all.xn--b6gac.eu.org', 'cdn.xn--b6gac.eu.org', 'cdn-b100.xn--b6gac.eu.org'];
let proxyIP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];

我们能否通过一个定时器,让worker每小时用当前proxyIP 去访问1次 google.com之类稳定的网站。

当前的proxyIP 失效时,自动找出替换到下一个有效的ip。

补充

抱歉,我并未去了解过worker相关的文档,也不确定worker能否允许这样做。

如果该功能不具备可行性,请麻烦留言,我会关闭该Issue。

v2rayN更新到V6.31之后,开启TUN模式,出现[31mERROR dns: exchange failed for ……

启动服务(2023/12/15 15:57:51)...
2023/12/15 15:57:52 系统代理设置改变ForcedChange
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for www.msftconnecttest.com. IN A: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for rr1---sn-p5qlsn6l.googlevideo.com. IN A: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:57:56 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for www.msftconnecttest.com. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for mtalk.google.com. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for www.google.com. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for rr1---sn-p5qlsn6l.googlevideo.com. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for rr1---sn-p5qlsn6l.googlevideo.com. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for www.youtube.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for mtalk.google.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for www.msftconnecttest.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for www.google.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for geo.prod.do.dsp.mp.microsoft.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:57 �[31mERROR�[0m dns: exchange failed for mtalk.google.com. IN A: write tcp 192.168.1.136:8981->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for www.google.com. IN A: context canceled
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for geo.prod.do.dsp.mp.microsoft.com. IN A: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for www.youtube.com. IN A: write tcp 192.168.1.136:8983->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for www.youtube.com. IN A: context canceled
+0800 2023-12-15 15:57:59 �[31mERROR�[0m dns: exchange failed for geo.prod.do.dsp.mp.microsoft.com. IN A: write tcp 192.168.1.136:8984->172.67.58.199:2095: use of closed network connection
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.google.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for mtalk.google.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.msftconnecttest.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for rr1---sn-p5qlsn6l.googlevideo.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN HTTPS: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.google.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.msftconnecttest.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for dns.google. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for rr1---sn-p5qlsn6l.googlevideo.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for mtalk.google.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for geo.prod.do.dsp.mp.microsoft.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.youtube.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for geo.prod.do.dsp.mp.microsoft.com. IN A: context canceled
+0800 2023-12-15 15:58:01 �[31mERROR�[0m dns: exchange failed for www.youtube.com. IN A: context canceled
2023/12/15 15:58:04 当前服务的真连接延迟: -1 ms

大佬你的代码总是不通,这个代码就好用点,不知道哪里不一样

总觉得有点不对劲,不如另一个代码的好用,这个一直不通

// version base on commit 43fad05dcdae3b723c53c226f8181fc5bd47223e, time is 2023-06-22 15:20:02 UTC.
// @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 = 'xxxx-xxxx-xxxx-xxxx-xxxxxxxxx';

let proxyIP = 'cdn-all.xn--b6gac.eu.org';

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}
/
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} 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) | 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}@${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}

################################################################
`;
}

做了限速吗

同样环境下zizifn/edgetunne大佬的可以跑满千兆,你的全天峰值只有200M左右~~

能否将https与http,写成2个不同的work.js

能不能写成2个不同的work.js , 一个用于没有域名的http的,一个用于有域名的https的,,而且那个打开的域名,我建议改成其它的,尽量不要用ZF部门的网站,我的域名被封了,被举报涉嫌诈骗!

小瑕疵

大佬 可不可以优化下 用域名就中显示HTTPS的节点 没用域名就只显示HTTP的节点 先在是不管套没套域名 都是显示两种类型的节点

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.