dns2 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/proxy-protocol.js +153 -0
- package/package.json +1 -1
- package/packet.js +105 -17
- package/server/tcp.js +71 -0
- package/server/udp.js +26 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// PROXY protocol parser (HAProxy/Nginx).
|
|
4
|
+
// Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
|
5
|
+
//
|
|
6
|
+
// parse(buffer) returns:
|
|
7
|
+
// - { header, headerLength } when a complete header is at the start of buffer
|
|
8
|
+
// - null when the buffer is a valid prefix but more bytes are needed
|
|
9
|
+
// and throws when the bytes are not a valid PROXY header.
|
|
10
|
+
|
|
11
|
+
const V2_SIGNATURE = Buffer.from([
|
|
12
|
+
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
|
|
13
|
+
0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
|
|
14
|
+
]);
|
|
15
|
+
const V1_PREFIX = Buffer.from('PROXY ');
|
|
16
|
+
const V1_MAX_LEN = 108;
|
|
17
|
+
|
|
18
|
+
const FAMILY = { 0x10: 'IPv4', 0x20: 'IPv6', 0x30: 'Unix' };
|
|
19
|
+
const TRANSPORT = { 0x01: 'STREAM', 0x02: 'DGRAM' };
|
|
20
|
+
|
|
21
|
+
function parse(buffer) {
|
|
22
|
+
if (buffer.length >= 12) {
|
|
23
|
+
if (buffer.slice(0, 12).equals(V2_SIGNATURE)) return parseV2(buffer);
|
|
24
|
+
} else if (V2_SIGNATURE.slice(0, buffer.length).equals(buffer)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (buffer.length >= 6) {
|
|
29
|
+
if (buffer.slice(0, 6).equals(V1_PREFIX)) return parseV1(buffer);
|
|
30
|
+
} else if (V1_PREFIX.slice(0, buffer.length).equals(buffer)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error('PROXY protocol: header missing or malformed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseV1(buffer) {
|
|
38
|
+
const search = buffer.slice(0, Math.min(buffer.length, V1_MAX_LEN));
|
|
39
|
+
const newline = search.indexOf('\r\n');
|
|
40
|
+
if (newline === -1) {
|
|
41
|
+
if (buffer.length >= V1_MAX_LEN) {
|
|
42
|
+
throw new Error('PROXY v1: header exceeds maximum length');
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const line = buffer.slice(0, newline).toString('ascii');
|
|
47
|
+
const parts = line.split(' ');
|
|
48
|
+
if (parts[0] !== 'PROXY') throw new Error('PROXY v1: malformed header');
|
|
49
|
+
const headerLength = newline + 2;
|
|
50
|
+
|
|
51
|
+
if (parts[1] === 'UNKNOWN') {
|
|
52
|
+
return { header: { version: 1, command: 'UNKNOWN' }, headerLength };
|
|
53
|
+
}
|
|
54
|
+
if (parts.length !== 6) throw new Error('PROXY v1: malformed header');
|
|
55
|
+
const [ , proto, sourceAddress, destinationAddress, srcPort, dstPort ] = parts;
|
|
56
|
+
if (proto !== 'TCP4' && proto !== 'TCP6') {
|
|
57
|
+
throw new Error(`PROXY v1: unsupported protocol ${proto}`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
header: {
|
|
61
|
+
version : 1,
|
|
62
|
+
command : 'PROXY',
|
|
63
|
+
family : proto === 'TCP4' ? 'IPv4' : 'IPv6',
|
|
64
|
+
transport : 'STREAM',
|
|
65
|
+
sourceAddress,
|
|
66
|
+
sourcePort : parseInt(srcPort, 10),
|
|
67
|
+
destinationAddress,
|
|
68
|
+
destinationPort : parseInt(dstPort, 10),
|
|
69
|
+
},
|
|
70
|
+
headerLength,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseV2(buffer) {
|
|
75
|
+
if (buffer.length < 16) return null;
|
|
76
|
+
const verCmd = buffer[12];
|
|
77
|
+
const version = verCmd >> 4;
|
|
78
|
+
const command = verCmd & 0x0F;
|
|
79
|
+
if (version !== 2) throw new Error(`PROXY v2: unsupported version ${version}`);
|
|
80
|
+
if (command !== 0 && command !== 1) {
|
|
81
|
+
throw new Error(`PROXY v2: unknown command ${command}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const famProto = buffer[13];
|
|
85
|
+
const addressLength = buffer.readUInt16BE(14);
|
|
86
|
+
const headerLength = 16 + addressLength;
|
|
87
|
+
if (buffer.length < headerLength) return null;
|
|
88
|
+
|
|
89
|
+
if (command === 0) {
|
|
90
|
+
// LOCAL — no real client info (e.g. proxy-originated health check).
|
|
91
|
+
return { header: { version: 2, command: 'LOCAL' }, headerLength };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const family = FAMILY[famProto & 0xF0];
|
|
95
|
+
const transport = TRANSPORT[famProto & 0x0F];
|
|
96
|
+
|
|
97
|
+
let sourceAddress, destinationAddress, sourcePort, destinationPort;
|
|
98
|
+
if (family === 'IPv4' && addressLength >= 12) {
|
|
99
|
+
sourceAddress = `${buffer[16]}.${buffer[17]}.${buffer[18]}.${buffer[19]}`;
|
|
100
|
+
destinationAddress = `${buffer[20]}.${buffer[21]}.${buffer[22]}.${buffer[23]}`;
|
|
101
|
+
sourcePort = buffer.readUInt16BE(24);
|
|
102
|
+
destinationPort = buffer.readUInt16BE(26);
|
|
103
|
+
} else if (family === 'IPv6' && addressLength >= 36) {
|
|
104
|
+
sourceAddress = ipv6FromBytes(buffer.slice(16, 32));
|
|
105
|
+
destinationAddress = ipv6FromBytes(buffer.slice(32, 48));
|
|
106
|
+
sourcePort = buffer.readUInt16BE(48);
|
|
107
|
+
destinationPort = buffer.readUInt16BE(50);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error(`PROXY v2: unsupported address family/protocol 0x${famProto.toString(16)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
header: {
|
|
114
|
+
version : 2,
|
|
115
|
+
command : 'PROXY',
|
|
116
|
+
family,
|
|
117
|
+
transport,
|
|
118
|
+
sourceAddress,
|
|
119
|
+
sourcePort,
|
|
120
|
+
destinationAddress,
|
|
121
|
+
destinationPort,
|
|
122
|
+
},
|
|
123
|
+
headerLength,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ipv6FromBytes(bytes) {
|
|
128
|
+
const segments = [];
|
|
129
|
+
for (let i = 0; i < 16; i += 2) {
|
|
130
|
+
segments.push(bytes.readUInt16BE(i).toString(16));
|
|
131
|
+
}
|
|
132
|
+
return segments.join(':');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Test helpers — build wire-format headers used by tests and example code.
|
|
136
|
+
function buildV1({ family = 'TCP4', sourceAddress, destinationAddress, sourcePort, destinationPort }) {
|
|
137
|
+
return Buffer.from(`PROXY ${family} ${sourceAddress} ${destinationAddress} ${sourcePort} ${destinationPort}\r\n`, 'ascii');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildV2Ipv4({ sourceAddress, destinationAddress, sourcePort, destinationPort, transport = 'STREAM' }) {
|
|
141
|
+
const buf = Buffer.alloc(16 + 12);
|
|
142
|
+
V2_SIGNATURE.copy(buf, 0);
|
|
143
|
+
buf[12] = 0x21; // version 2 | PROXY command
|
|
144
|
+
buf[13] = 0x10 | (transport === 'DGRAM' ? 0x02 : 0x01); // IPv4 | STREAM/DGRAM
|
|
145
|
+
buf.writeUInt16BE(12, 14);
|
|
146
|
+
sourceAddress.split('.').forEach((o, i) => { buf[16 + i] = parseInt(o, 10); });
|
|
147
|
+
destinationAddress.split('.').forEach((o, i) => { buf[20 + i] = parseInt(o, 10); });
|
|
148
|
+
buf.writeUInt16BE(sourcePort, 24);
|
|
149
|
+
buf.writeUInt16BE(destinationPort, 26);
|
|
150
|
+
return buf;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { parse, parseV1, parseV2, buildV1, buildV2Ipv4 };
|
package/package.json
CHANGED
package/packet.js
CHANGED
|
@@ -5,10 +5,29 @@ const BufferWriter = require('./lib/writer');
|
|
|
5
5
|
|
|
6
6
|
const debug = debuglog('dns2');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// Canonical IPv6 text form per RFC 5952:
|
|
9
|
+
// - lower case hex, no leading zeros per group (handled by toString(16))
|
|
10
|
+
// - the longest run of >= 2 zero groups is replaced with "::"
|
|
11
|
+
// - on ties, the first such run is chosen
|
|
12
|
+
// - a single zero group is NOT compressed
|
|
13
|
+
const toIPv6 = buffer => {
|
|
14
|
+
const segments = buffer.map(part => (part > 0 ? part.toString(16) : '0'));
|
|
15
|
+
let bestStart = -1; let bestLen = 0;
|
|
16
|
+
let curStart = -1; let curLen = 0;
|
|
17
|
+
for (let i = 0; i < segments.length; i++) {
|
|
18
|
+
if (segments[i] === '0') {
|
|
19
|
+
if (curLen === 0) curStart = i;
|
|
20
|
+
curLen++;
|
|
21
|
+
if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; }
|
|
22
|
+
} else {
|
|
23
|
+
curLen = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (bestLen < 2) return segments.join(':');
|
|
27
|
+
const before = segments.slice(0, bestStart).join(':');
|
|
28
|
+
const after = segments.slice(bestStart + bestLen).join(':');
|
|
29
|
+
return `${before}::${after}`;
|
|
30
|
+
};
|
|
12
31
|
|
|
13
32
|
const fromIPv6 = (address) => {
|
|
14
33
|
const digits = address.split(':');
|
|
@@ -240,8 +259,11 @@ Packet.Header = function(header) {
|
|
|
240
259
|
this.rd = 0;
|
|
241
260
|
this.ra = 0;
|
|
242
261
|
this.z = 0;
|
|
262
|
+
this.ad = 0;
|
|
263
|
+
this.cd = 0;
|
|
243
264
|
this.rcode = 0;
|
|
244
265
|
this.qdcount = 0;
|
|
266
|
+
this.ancount = 0;
|
|
245
267
|
this.nscount = 0;
|
|
246
268
|
this.arcount = 0;
|
|
247
269
|
for (const k in header) {
|
|
@@ -267,7 +289,10 @@ Packet.Header.parse = function(reader) {
|
|
|
267
289
|
header.tc = reader.read(1);
|
|
268
290
|
header.rd = reader.read(1);
|
|
269
291
|
header.ra = reader.read(1);
|
|
270
|
-
|
|
292
|
+
// RFC 4035 §3.2.3 repurposed the second and third Z bits as AD and CD.
|
|
293
|
+
header.z = reader.read(1);
|
|
294
|
+
header.ad = reader.read(1);
|
|
295
|
+
header.cd = reader.read(1);
|
|
271
296
|
header.rcode = reader.read(4);
|
|
272
297
|
header.qdcount = reader.read(16);
|
|
273
298
|
header.ancount = reader.read(16);
|
|
@@ -289,7 +314,9 @@ Packet.Header.prototype.toBuffer = function(writer) {
|
|
|
289
314
|
writer.write(this.tc, 1);
|
|
290
315
|
writer.write(this.rd, 1);
|
|
291
316
|
writer.write(this.ra, 1);
|
|
292
|
-
writer.write(this.z,
|
|
317
|
+
writer.write(this.z, 1);
|
|
318
|
+
writer.write(this.ad, 1);
|
|
319
|
+
writer.write(this.cd, 1);
|
|
293
320
|
writer.write(this.rcode, 4);
|
|
294
321
|
writer.write(this.qdcount, 16);
|
|
295
322
|
writer.write(this.ancount, 16);
|
|
@@ -457,11 +484,18 @@ Packet.Name = {
|
|
|
457
484
|
reader = new Packet.Reader(reader);
|
|
458
485
|
}
|
|
459
486
|
const name = []; let o; let len = reader.read(8);
|
|
487
|
+
// Track each pointer target we follow. A crafted packet can chain
|
|
488
|
+
// pointers in a cycle; without this guard, decode would loop forever.
|
|
489
|
+
const visited = new Set();
|
|
460
490
|
while (len) {
|
|
461
491
|
if ((len & Packet.Name.COPY) === Packet.Name.COPY) {
|
|
462
492
|
len -= Packet.Name.COPY;
|
|
463
493
|
len = len << 8;
|
|
464
494
|
const pos = len + reader.read(8);
|
|
495
|
+
if (visited.has(pos)) {
|
|
496
|
+
throw new Error('Name decode: pointer cycle detected');
|
|
497
|
+
}
|
|
498
|
+
visited.add(pos);
|
|
465
499
|
if (!o) o = reader.offset;
|
|
466
500
|
reader.offset = pos * 8;
|
|
467
501
|
len = reader.read(8);
|
|
@@ -740,19 +774,42 @@ Packet.Resource.SRV = {
|
|
|
740
774
|
},
|
|
741
775
|
};
|
|
742
776
|
|
|
743
|
-
|
|
777
|
+
// RFC 6891 §6.1.3 — the OPT record's TTL field carries:
|
|
778
|
+
// bits 0- 7: extended RCODE (high byte of a 12-bit RCODE)
|
|
779
|
+
// bits 8-15: EDNS version
|
|
780
|
+
// bit 16: DO (DNSSEC OK)
|
|
781
|
+
// bits 17-31: reserved Z, must be zero
|
|
782
|
+
const ednsTtl = (extendedRcode, version, doFlag) =>
|
|
783
|
+
(((extendedRcode & 0xff) << 24) >>> 0)
|
|
784
|
+
| ((version & 0xff) << 16)
|
|
785
|
+
| (doFlag ? 0x8000 : 0);
|
|
786
|
+
|
|
787
|
+
Packet.Resource.EDNS = function(rdata, opts = {}) {
|
|
788
|
+
const extendedRcode = opts.extendedRcode || 0;
|
|
789
|
+
const version = opts.version || 0;
|
|
790
|
+
const doFlag = !!opts.doFlag;
|
|
791
|
+
const udpPayloadSize = opts.udpPayloadSize || 512;
|
|
744
792
|
return {
|
|
745
793
|
type : Packet.TYPE.EDNS,
|
|
746
|
-
class :
|
|
747
|
-
ttl :
|
|
794
|
+
class : udpPayloadSize,
|
|
795
|
+
ttl : ednsTtl(extendedRcode, version, doFlag),
|
|
796
|
+
extendedRcode,
|
|
797
|
+
version,
|
|
798
|
+
doFlag,
|
|
748
799
|
rdata, // Objects of type Packet.Resource.EDNS.*
|
|
749
800
|
};
|
|
750
801
|
};
|
|
751
802
|
|
|
752
803
|
Packet.Resource.EDNS.decode = function(reader, length) {
|
|
753
|
-
this.type
|
|
754
|
-
|
|
755
|
-
this.
|
|
804
|
+
// When invoked through Resource.parse, this.type/class/ttl are already set
|
|
805
|
+
// from the wire. Direct callers (e.g. unit tests) hit defaults instead.
|
|
806
|
+
this.type = this.type ?? Packet.TYPE.EDNS;
|
|
807
|
+
this.class = this.class ?? 512;
|
|
808
|
+
const ttl = this.ttl ?? 0;
|
|
809
|
+
this.ttl = ttl;
|
|
810
|
+
this.extendedRcode = (ttl >>> 24) & 0xff;
|
|
811
|
+
this.version = (ttl >>> 16) & 0xff;
|
|
812
|
+
this.doFlag = !!(ttl & 0x8000);
|
|
756
813
|
this.rdata = [];
|
|
757
814
|
|
|
758
815
|
while (length) {
|
|
@@ -845,16 +902,47 @@ Packet.Resource.EDNS.ECS.decode = function(reader, length) {
|
|
|
845
902
|
};
|
|
846
903
|
|
|
847
904
|
Packet.Resource.EDNS.ECS.encode = function(record, writer) {
|
|
848
|
-
|
|
905
|
+
// RFC 7871 §6: the ADDRESS field carries only the leftmost
|
|
906
|
+
// ceil(sourcePrefixLength / 8) octets.
|
|
907
|
+
const octets = Math.ceil(record.sourcePrefixLength / 8);
|
|
849
908
|
writer.write(record.family, 16);
|
|
850
909
|
writer.write(record.sourcePrefixLength, 8);
|
|
851
910
|
writer.write(record.scopePrefixLength, 8);
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
911
|
+
let bytes;
|
|
912
|
+
if (record.family === 1) {
|
|
913
|
+
bytes = record.ip.split('.').map(s => parseInt(s, 10) || 0);
|
|
914
|
+
} else if (record.family === 2) {
|
|
915
|
+
bytes = expandIPv6ToBytes(record.ip);
|
|
916
|
+
} else {
|
|
917
|
+
throw new Error(`EDNS.ECS encode: unsupported family ${record.family}`);
|
|
918
|
+
}
|
|
919
|
+
for (let i = 0; i < octets; i++) {
|
|
920
|
+
writer.write(bytes[i] || 0, 8);
|
|
921
|
+
}
|
|
856
922
|
};
|
|
857
923
|
|
|
924
|
+
// Expand a (possibly compressed) IPv6 text address into a 16-byte array.
|
|
925
|
+
function expandIPv6ToBytes(address) {
|
|
926
|
+
let head, tail;
|
|
927
|
+
const idx = address.indexOf('::');
|
|
928
|
+
if (idx === -1) {
|
|
929
|
+
head = address.split(':');
|
|
930
|
+
tail = [];
|
|
931
|
+
} else {
|
|
932
|
+
head = address.slice(0, idx).split(':').filter(Boolean);
|
|
933
|
+
tail = address.slice(idx + 2).split(':').filter(Boolean);
|
|
934
|
+
}
|
|
935
|
+
const missing = 8 - head.length - tail.length;
|
|
936
|
+
const groups = [ ...head, ...new Array(missing).fill('0'), ...tail ];
|
|
937
|
+
const out = new Array(16).fill(0);
|
|
938
|
+
for (let g = 0; g < 8; g++) {
|
|
939
|
+
const n = parseInt(groups[g], 16) || 0;
|
|
940
|
+
out[g * 2] = (n >> 8) & 0xff;
|
|
941
|
+
out[g * 2 + 1] = n & 0xff;
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
|
|
858
946
|
Packet.Resource.CAA = {
|
|
859
947
|
encode: function(record, writer) {
|
|
860
948
|
writer = writer || new Packet.Writer();
|
package/server/tcp.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
const tcp = require('node:net');
|
|
2
2
|
const Packet = require('../packet');
|
|
3
|
+
const proxyProtocol = require('../lib/proxy-protocol');
|
|
3
4
|
|
|
4
5
|
class Server extends tcp.Server {
|
|
5
6
|
constructor(options) {
|
|
6
7
|
super();
|
|
8
|
+
let proxyProtocolEnabled = false;
|
|
9
|
+
if (typeof options === 'object' && options !== null) {
|
|
10
|
+
proxyProtocolEnabled = options.proxyProtocol ?? false;
|
|
11
|
+
}
|
|
7
12
|
if (typeof options === 'function') {
|
|
8
13
|
this.on('request', options);
|
|
9
14
|
}
|
|
15
|
+
this.proxyProtocol = proxyProtocolEnabled;
|
|
10
16
|
this.on('connection', this.handle.bind(this));
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
async handle(client) {
|
|
14
20
|
try {
|
|
21
|
+
if (this.proxyProtocol) {
|
|
22
|
+
const header = await consumeProxyHeader(client);
|
|
23
|
+
client.proxy = header;
|
|
24
|
+
if (header.command === 'PROXY') {
|
|
25
|
+
client.proxyAddress = header.sourceAddress;
|
|
26
|
+
client.proxyPort = header.sourcePort;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
15
29
|
const data = await Packet.readStream(client);
|
|
16
30
|
const message = Packet.parse(data);
|
|
17
31
|
this.emit('request', message, this.response.bind(this, client), client);
|
|
@@ -31,4 +45,61 @@ class Server extends tcp.Server {
|
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
// Read and consume the PROXY header from the front of the socket's stream.
|
|
49
|
+
// Any bytes that arrive past the header are unshifted back into the socket
|
|
50
|
+
// so the next reader (Packet.readStream) sees them.
|
|
51
|
+
function consumeProxyHeader(socket) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let chunklen = 0;
|
|
55
|
+
let done = false;
|
|
56
|
+
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
socket.removeListener('readable', onReadable);
|
|
59
|
+
socket.removeListener('end', onEnd);
|
|
60
|
+
socket.removeListener('error', onError);
|
|
61
|
+
};
|
|
62
|
+
const onError = err => {
|
|
63
|
+
if (done) return;
|
|
64
|
+
done = true;
|
|
65
|
+
cleanup();
|
|
66
|
+
reject(err);
|
|
67
|
+
};
|
|
68
|
+
const onEnd = () => {
|
|
69
|
+
if (done) return;
|
|
70
|
+
done = true;
|
|
71
|
+
cleanup();
|
|
72
|
+
reject(new Error('PROXY protocol: stream ended before header complete'));
|
|
73
|
+
};
|
|
74
|
+
const onReadable = () => {
|
|
75
|
+
if (done) return;
|
|
76
|
+
let chunk;
|
|
77
|
+
while ((chunk = socket.read()) !== null) {
|
|
78
|
+
chunks.push(chunk);
|
|
79
|
+
chunklen += chunk.length;
|
|
80
|
+
}
|
|
81
|
+
if (chunklen === 0) return;
|
|
82
|
+
const buffer = Buffer.concat(chunks, chunklen);
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = proxyProtocol.parse(buffer);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return onError(e);
|
|
88
|
+
}
|
|
89
|
+
if (!parsed) return;
|
|
90
|
+
done = true;
|
|
91
|
+
cleanup();
|
|
92
|
+
const leftover = buffer.slice(parsed.headerLength);
|
|
93
|
+
if (leftover.length) socket.unshift(leftover);
|
|
94
|
+
resolve(parsed.header);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
socket.on('readable', onReadable);
|
|
98
|
+
socket.on('end', onEnd);
|
|
99
|
+
socket.on('error', onError);
|
|
100
|
+
// Drain anything already buffered before our 'readable' listener attached.
|
|
101
|
+
onReadable();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
34
105
|
module.exports = Server;
|
package/server/udp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const udp = require('node:dgram');
|
|
2
2
|
const Packet = require('../packet');
|
|
3
|
+
const proxyProtocol = require('../lib/proxy-protocol');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* [Server description]
|
|
@@ -9,10 +10,13 @@ const Packet = require('../packet');
|
|
|
9
10
|
class Server extends udp.Socket {
|
|
10
11
|
constructor(options) {
|
|
11
12
|
let type = 'udp4';
|
|
12
|
-
|
|
13
|
+
let proxyProtocolEnabled = false;
|
|
14
|
+
if (typeof options === 'object' && options !== null) {
|
|
13
15
|
type = options.type ?? type;
|
|
16
|
+
proxyProtocolEnabled = options.proxyProtocol ?? false;
|
|
14
17
|
}
|
|
15
18
|
super(type);
|
|
19
|
+
this.proxyProtocol = proxyProtocolEnabled;
|
|
16
20
|
if (typeof options === 'function') {
|
|
17
21
|
this.on('request', options);
|
|
18
22
|
}
|
|
@@ -21,8 +25,28 @@ class Server extends udp.Socket {
|
|
|
21
25
|
|
|
22
26
|
handle(data, rinfo) {
|
|
23
27
|
try {
|
|
28
|
+
// Response is always sent back to the immediate sender (the proxy when
|
|
29
|
+
// proxyProtocol is enabled); the parsed client info is exposed to the
|
|
30
|
+
// request handler so it can log/authorize against the real peer.
|
|
31
|
+
const responder = rinfo;
|
|
32
|
+
let clientInfo = rinfo;
|
|
33
|
+
if (this.proxyProtocol) {
|
|
34
|
+
const parsed = proxyProtocol.parse(data);
|
|
35
|
+
if (!parsed) throw new Error('PROXY protocol: incomplete header');
|
|
36
|
+
if (parsed.header.command === 'PROXY') {
|
|
37
|
+
clientInfo = {
|
|
38
|
+
...rinfo,
|
|
39
|
+
address : parsed.header.sourceAddress,
|
|
40
|
+
port : parsed.header.sourcePort,
|
|
41
|
+
proxy : parsed.header,
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
clientInfo = { ...rinfo, proxy: parsed.header };
|
|
45
|
+
}
|
|
46
|
+
data = data.slice(parsed.headerLength);
|
|
47
|
+
}
|
|
24
48
|
const message = Packet.parse(data);
|
|
25
|
-
this.emit('request', message, this.response.bind(this,
|
|
49
|
+
this.emit('request', message, this.response.bind(this, responder), clientInfo);
|
|
26
50
|
} catch (e) {
|
|
27
51
|
this.emit('requestError', e);
|
|
28
52
|
}
|