利用Cloudflare Worker免费搭建科学上网节点,上网速度是真的快!

介绍

利用Cloudflare Worker搭建免费科学上网的节点,我测试了一下,这速度是真的快,轻松突破20w+。有了这个,谁还自己搭建VPS!一般的机场也可能达不到这样的速度。

 

所需工具

Cloudflare账号

去Cloudflare注册一个账号,注册网址:https://dash.cloudflare.com/sign-up 。后面所有的部署操作都需要用到它。

科学上网工具

Clash for windows/mac/android 访问密码: 4499

搭建步骤

1.登录Cloudflare,找到Workers & Pages并点击创建Create application.

 

2.点击Create Worker,更改名字,点击Deploy

 

修改名字不是必须的,只是方便自己识别

 

3.编辑脚本

 

删除原来的内容,并复制下面的新脚本到这个文件中。

 

 

下面是新的脚本,点击右边的复制按钮复制内容。并将内容粘贴到worker.js中

@ts-ignore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
import { connect } from 'cloudflare:sockets';


let userID = 'f974a9f4-7bcb-4961-9913-f87e75984635';

let proxyIP = '';// 小白勿动,该地址并不影响你的网速,这是给CF代理使用的。'cdn.xn--b6gac.eu.org', 'cdn-all.xn--b6gac.eu.org', 'edgetunnel.anycast.eu.org'

//let sub = '';// 留空则显示原版内容
let sub = 'vless-4ca.pages.dev';// 内置优选订阅生成器,可自行搭建 https://github.com/cmliu/WorkerVless2sub
let subconverter = 'api.v1.mk';// clash订阅转换后端,目前使用肥羊的订阅转换功能。自带虚假uuid和host订阅。
let subconfig = "https://raw.githubusercontent.com/cmliu/ACL4SSR/main/Clash/config/ACL4SSR_Online_Full_MultiMode.ini"; //订阅配置文件
// The user name and password do not contain special characters
// Setting the address will ignore proxyIP
// Example: user:pass@host:port or host:port
let socks5Address = '';
let RproxyIP = 'false';
if (!isValidUUID(userID)) {
throw new Error('uuid is not valid');
}

let parsedSocks5Address = {};
let enableSocks = false;

// 虚假uuid和hostname,用于发送给配置生成服务
let fakeUserID = generateUUID();
let fakeHostName = generateRandomString();
let tls = true;
export default {
async fetch(request, env, ctx) {
try {
const userAgent = request.headers.get('User-Agent').toLowerCase();
userID = (env.UUID || userID).toLowerCase();
proxyIP = env.PROXYIP || proxyIP;
socks5Address = env.SOCKS5 || socks5Address;
sub = env.SUB || sub;
subconverter = env.SUBAPI || subconverter;
subconfig = env.SUBCONFIG || subconfig;
if (socks5Address) {
RproxyIP = env.RPROXYIP || 'false';
try {
parsedSocks5Address = socks5AddressParser(socks5Address);
enableSocks = true;
} catch (err) {
let e = err;
console.log(e.toString());
enableSocks = false;
}
} else {
RproxyIP = env.RPROXYIP || !proxyIP ? 'true' : 'false';
}
const upgradeHeader = request.headers.get('Upgrade');
const url = new URL(request.url);
if (url.searchParams.has('notls')) tls = false;
if (!upgradeHeader || upgradeHeader !== 'websocket') {
// const url = new URL(request.url);
switch (url.pathname.toLowerCase()) {
case '/':
return new Response(JSON.stringify(request.cf), { status: 200 });
case `/${userID}`: {
const vlessConfig = await getVLESSConfig(userID, request.headers.get('Host'), sub, userAgent, RproxyIP);
const now = Date.now();
const timestamp = Math.floor(now / 1000);
const expire = 4102329600;//2099-12-31
const today = new Date(now);
today.setHours(0, 0, 0, 0);
const UD = Math.floor(((now - today.getTime())/86400000) * 24 * 1099511627776 / 2);
if (userAgent && userAgent.includes('mozilla')){
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Type": "text/plain;charset=utf-8",
}
});
} else {
return new Response(`${vlessConfig}`, {
status: 200,
headers: {
"Content-Disposition": "attachment; filename=edgetunnel; filename*=utf-8''edgetunnel",
"Content-Type": "text/plain;charset=utf-8",
"Profile-Update-Interval": "6",
"Subscription-Userinfo": `upload=${UD}; download=${UD}; total=${24 * 1099511627776}; expire=${expire}`,
}
});
}
}
default:
return new Response('Not found', { status: 404 });
}
} else {
if (new RegExp('/proxyip=', 'i').test(url.pathname)) proxyIP = url.pathname.split("=")[1];
else if (new RegExp('/proxyip.', 'i').test(url.pathname)) proxyIP = url.pathname.split("/proxyip.")[1];
else if (!proxyIP || proxyIP == '') proxyIP = 'proxyip.fxxk.dedyn.io';
return await vlessOverWSHandler(request);
}
} catch (err) {
let e = err;
return new Response(e.toString());
}
},
};
async function vlessOverWSHandler(request) {

// @ts-ignore
const webSocketPair = new WebSocketPair();
const [client, webSocket] = Object.values(webSocketPair);

webSocket.accept();

let address = '';
let portWithRandomLog = '';
const log = ( info, event) => {
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
};
const earlyDataHeader = request.headers.get('sec-websocket-protocol') || '';

const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log);

let remoteSocketWapper = {
value: null,
};
let isDns = false;

// ws --> remote
readableWebSocketStream.pipeTo(new WritableStream({
async write(chunk, controller) {
if (isDns) {
return await handleDNSQuery(chunk, webSocket, null, log);
}
if (remoteSocketWapper.value) {
const writer = remoteSocketWapper.value.writable.getWriter()
await writer.write(chunk);
writer.releaseLock();
return;
}

const {
hasError,
message,
addressType,
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);

if (isDns) {
return handleDNSQuery(rawClientData, webSocket, vlessResponseHeader, log);
}
handleTCPOutBound(remoteSocketWapper, addressType, 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,
});
}

async function handleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) {
async function connectAndWrite(address, port, socks = false) {
const tcpSocket = socks ? await socks5Connect(addressType, address, port, log)
: connect({
hostname: address,
port: port,
});
remoteSocket.value = tcpSocket;
log(`connected to ${address}:${port}`);
const writer = tcpSocket.writable.getWriter();
await writer.write(rawClientData); // first write, normal 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() {
if (enableSocks) {
tcpSocket = await connectAndWrite(addressRemote, portRemote, true);
} else {
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);
}

let tcpSocket = await connectAndWrite(addressRemote, portRemote);

// when remoteSocket is ready, pass to websocket
// remote--> ws
remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log);
}

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;

}


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,
};
}


async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) {
// remote--> ws
let remoteChunkCount = 0;
let chunks = [];
let vlessHeader = vlessResponseHeader;
let hasIncomingData = false; // check if remoteSocket has incoming data
await remoteSocket.readable
.pipeTo(
new WritableStream({
start() {
},
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();
}
}

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

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

async function handleDNSQuery(udpChunk, webSocket, vlessResponseHeader, log) {
// no matter which DNS server client send, we alwasy use hard code one.
// beacsue someof DNS server is not support DNS over TCP
try {
const dnsServer = '8.8.4.4'; // change to 1.1.1.1 after cf fix connect own ip bug
const dnsPort = 53;
let vlessHeader = vlessResponseHeader;
const tcpSocket = connect({
hostname: dnsServer,
port: dnsPort,
});

log(`connected to ${dnsServer}:${dnsPort}`);
const writer = tcpSocket.writable.getWriter();
await writer.write(udpChunk);
writer.releaseLock();
await tcpSocket.readable.pipeTo(new WritableStream({
async write(chunk) {
if (webSocket.readyState === WS_READY_STATE_OPEN) {
if (vlessHeader) {
webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer());
vlessHeader = null;
} else {
webSocket.send(chunk);
}
}
},
close() {
log(`dns server(${dnsServer}) tcp is close`);
},
abort(reason) {
console.error(`dns server(${dnsServer}) tcp is abort`, reason);
},
}));
} catch (error) {
console.error(
`handleDNSQuery have exception, error: ${error.message}`
);
}
}

async function socks5Connect(addressType, addressRemote, portRemote, log) {
const { username, password, hostname, port } = parsedSocks5Address;
// Connect to the SOCKS server
const socket = connect({
hostname,
port,
});

// Request head format (Worker -> Socks Server):
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+

// https://en.wikipedia.org/wiki/SOCKS#SOCKS5
// For METHODS:
// 0x00 NO AUTHENTICATION REQUIRED
// 0x02 USERNAME/PASSWORD https://datatracker.ietf.org/doc/html/rfc1929
const socksGreeting = new Uint8Array([5, 2, 0, 2]);

const writer = socket.writable.getWriter();

await writer.write(socksGreeting);
log('sent socks greeting');

const reader = socket.readable.getReader();
const encoder = new TextEncoder();
let res = (await reader.read()).value;
// Response format (Socks Server -> Worker):
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
if (res[0] !== 0x05) {
log(`socks server version error: ${res[0]} expected: 5`);
return;
}
if (res[1] === 0xff) {
log("no acceptable methods");
return;
}

// if return 0x0502
if (res[1] === 0x02) {
log("socks server needs auth");
if (!username || !password) {
log("please provide username/password");
return;
}
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
const authRequest = new Uint8Array([
1,
username.length,
...encoder.encode(username),
password.length,
...encoder.encode(password)
]);
await writer.write(authRequest);
res = (await reader.read()).value;
// expected 0x0100
if (res[0] !== 0x01 || res[1] !== 0x00) {
log("fail to auth socks server");
return;
}
}

// Request data format (Worker -> Socks Server):
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// ATYP: address type of following address
// 0x01: IPv4 address
// 0x03: Domain name
// 0x04: IPv6 address
// DST.ADDR: desired destination address
// DST.PORT: desired destination port in network octet order

// addressType
// 1--> ipv4 addressLength =4
// 2--> domain name
// 3--> ipv6 addressLength =16
let DSTADDR; // DSTADDR = ATYP + DST.ADDR
switch (addressType) {
case 1:
DSTADDR = new Uint8Array(
[1, ...addressRemote.split('.').map(Number)]
);
break;
case 2:
DSTADDR = new Uint8Array(
[3, addressRemote.length, ...encoder.encode(addressRemote)]
);
break;
case 3:
DSTADDR = new Uint8Array(
[4, ...addressRemote.split(':').flatMap(x => [parseInt(x.slice(0, 2), 16), parseInt(x.slice(2), 16)])]
);
break;
default:
log(`invild addressType is ${addressType}`);
return;
}
const socksRequest = new Uint8Array([5, 1, 0, ...DSTADDR, portRemote >> 8, portRemote & 0xff]);
await writer.write(socksRequest);
log('sent socks request');

res = (await reader.read()).value;
// Response format (Socks Server -> Worker):
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
if (res[1] === 0x00) {
log("socks connection opened");
} else {
log("fail to open socks connection");
return;
}
writer.releaseLock();
reader.releaseLock();
return socket;
}


function socks5AddressParser(address) {
let [latter, former] = address.split("@").reverse();
let username, password, hostname, port;
if (former) {
const formers = former.split(":");
if (formers.length !== 2) {
throw new Error('Invalid SOCKS address format');
}
[username, password] = formers;
}
const latters = latter.split(":");
port = Number(latters.pop());
if (isNaN(port)) {
throw new Error('Invalid SOCKS address format');
}
hostname = latters.join(":");
const regex = /^\[.*\]$/;
if (hostname.includes(":") && !regex.test(hostname)) {
throw new Error('Invalid SOCKS address format');
}
return {
username,
password,
hostname,
port,
}
}

function revertFakeInfo(content, userID, hostName, isBase64) {
if (isBase64) content = atob(content);//Base64解码
content = content.replace(new RegExp(fakeUserID, 'g'), userID).replace(new RegExp(fakeHostName, 'g'), hostName);
if (isBase64) content = btoa(content);//Base64编码

return content;
}

function generateRandomNumber() {
let minNum = 100000;
let maxNum = 999999;
return Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum;
}

function generateRandomString() {
let minLength = 2;
let maxLength = 3;
let length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
let characters = 'abcdefghijklmnopqrstuvwxyz';
let result = '';
for (let i = 0; i < length; i++) {
result += characters[Math.floor(Math.random() * characters.length)];
}
return result;
}

function generateUUID() {
let uuid = '';
for (let i = 0; i < 32; i++) {
let num = Math.floor(Math.random() * 16);
if (num < 10) {
uuid += num;
} else {
uuid += String.fromCharCode(num + 55);
}
}
return uuid.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5').toLowerCase();
}

async function getVLESSConfig(userID, hostName, sub, userAgent, RproxyIP) {
// 如果sub为空,则显示原始内容
if (!sub || sub === '') {
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}
---------------------------------------------------------------
################################################################
`;
} else if (sub && userAgent.includes('mozilla') && !userAgent.includes('linux x86')) {
const vlessMain = `vless://${userID}@${hostName}:443?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`;

return `
################################################################
Subscribe / sub 订阅地址, 支持 Base64、clash-meta、sing-box 订阅格式, 您的订阅内容由 ${sub} 提供维护支持, 自动获取ProxyIP: ${RproxyIP}.
---------------------------------------------------------------
https://${hostName}/${userID}
---------------------------------------------------------------
################################################################
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}
---------------------------------------------------------------
################################################################
---------------------------------------------------------------
---------------------------------------------------------------
################################################################
`;
} else {
if (typeof fetch != 'function') {
return 'Error: fetch is not available in this environment.';
}
// 如果是使用默认域名,则改成一个workers的域名,订阅器会加上代理
if (hostName.includes(".workers.dev")){
fakeHostName = `${fakeHostName}.${generateRandomString()}${generateRandomNumber()}.workers.dev`;
} else if (hostName.includes(".pages.dev")){
fakeHostName = `${fakeHostName}.${generateRandomString()}${generateRandomNumber()}.pages.dev`;
} else if (hostName.includes("worker") || hostName.includes("notls") || tls == false){
fakeHostName = `notls.${fakeHostName}${generateRandomNumber()}.net`;
} else {
fakeHostName = `${fakeHostName}.${generateRandomNumber()}.xyz`
}
let content = "";
let url = "";
let isBase64 = false;
if (userAgent.includes('clash') && !userAgent.includes('nekobox')) {
url = `https://${subconverter}/sub?target=clash&url=https%3A%2F%2F${sub}%2Fsub%3Fhost%3D${fakeHostName}%26uuid%3D${fakeUserID}%26edgetunnel%3Dcmliu%26proxyip%3D${RproxyIP}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
} else if (userAgent.includes('sing-box') || userAgent.includes('singbox')) {
url = `https://${subconverter}/sub?target=singbox&url=https%3A%2F%2F${sub}%2Fsub%3Fhost%3D${fakeHostName}%26uuid%3D${fakeUserID}%26edgetunnel%3Dcmliu%26proxyip%3D${RproxyIP}&insert=false&config=${encodeURIComponent(subconfig)}&emoji=true&list=false&tfo=false&scv=true&fdn=false&sort=false&new_name=true`;
} else {
url = `https://${sub}/sub?host=${fakeHostName}&uuid=${fakeUserID}&edgetunnel=cmliu&proxyip=${RproxyIP}`;
isBase64 = true;
}
try {
const response = await fetch(url ,{
headers: {
'User-Agent': 'CF-Workers-edgetunnel/cmliu'
}});
content = await response.text();
return revertFakeInfo(content, userID, hostName, isBase64);
} catch (error) {
console.error('Error fetching content:', error);
return `Error fetching content: ${error.message}`;
}
}
}

4.生成并更换UUID

只需要更改脚本文件中的userID。可以到网上找一个在线生成UUID的网站直接生成,拿到新的替换即可。
这个UUID后面访问的时候需要使用,可以保存一下。
在线生成UUID:点击跳转

 

 

 

5.保存并部署

 

6.访问及使用

复制刚才更改的UUID,点击右上角的workers.dev

如果点击workers.dev打开的网页一直没有响应。请开启科学上网工具访问

 

在打开的网页地址后面输入”/您的UUID”

 

 

clash添加订阅
配置->托管配置->管理

 
添加订阅

 

 

 

 

进阶教程

pages方式部署项目

1.从开源项目下载项目源文件:下载地址

 
2.登录Cloudflare,找到Workers & Pages并点击创建Create application.选择Pages,点击上传资产,把上一步下载的压缩包上传

 

 

 

 

 

 

 

 
UUID可以参考前面的步骤去获取

 

 

 

 

 

申请免费域名

免费域名申请地址:点击跳转

 
到你的邮箱中,找到刚收到的邮件,点击确认

 
设置你的登录密码

 
再次输入你的邮箱,但是会提示你已经确认过了,这个时候,点击下面的登录,输入你的邮箱和设置的密码登录即可。

 

 
复制你的域名,回到cf中,点击自自定义域

 

 

这里填写的域名格式是:自定义名字+”.”+免费域名
比如:你申请的域名是xxx.dynv6.net,你自定义的名字是:free。那么填写:free.xxx.dynv6.net

 

 

 

 

cf复制的目标内容,一定要在最后加一个点
比如复制的内容是:free-d93.pages.dev,到域名的解析的data中填写:free-d93.pages.dev.

 

 

 

 

 

博主点评

优点:免费,速度快
缺点:他的IP显示的是国内IP,对于一些特殊的网站,比如ChatGPT可能无法使用

相关视频

推荐:低至6元/100G/月机场

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者,更多功能请访问博客站



利用Cloudflare Worker免费搭建科学上网节点,上网速度是真的快!
https://blog.fxcxy.com/2024/03/29/CloudflareWorker免费搭建科学上网节点/
作者
spatacus
发布于
2024年3月29日
许可协议