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
|
// shunt - link layer decoder
//
// Ethernet, optional VLAN tag, IPv4/IPv6, UDP - down to the DNS payload.
// Rejects anything malformed rather than guessing.
//
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Dirk Brenken <dev@brenken.org>
// How far to walk before giving up: stacked VLAN tags, IPv6 extension headers.
export const LIM = {
vlan: 3,
ext: 8
};
// Decoder verdicts, contract like the parser's.
export const ERR = {
SHORT: 'E_SHORT',
ETHER: 'E_ETHER',
VLAN: 'E_VLAN',
IPLEN: 'E_IPLEN',
FRAG: 'E_FRAG',
EXTHDR: 'E_EXTHDR',
PROTO: 'E_PROTO',
UDPLEN: 'E_UDPLEN'
};
const ETH_HDR = 14;
const ETYPE_OFF = 12;
const VLAN_TAG = 4;
const ET_IPV4 = 0x0800;
const ET_IPV6 = 0x86dd;
const ET_VLAN = 0x8100;
const ET_QINQ = 0x88a8;
const IP4_MIN = 20;
const IP6_HDR = 40;
const EXT_MIN = 8;
const UDP_HDR = 8;
const IP_UDP = 17;
const IP_FRAG = 44;
const IP_AH = 51;
const IP4_FRAG_MASK = 0x3fff;
function u16(buf, off) {
return (ord(buf, off) << 8) | ord(buf, off + 1);
}
function is_ext(proto) {
return proto == 0 || proto == 43 || proto == 60 || proto == IP_AH;
}
export function decap(buf) {
let len = length(buf ?? '');
if (len < ETH_HDR)
return { ok: false, err: ERR.SHORT };
let off = ETYPE_OFF, et = u16(buf, off), tags = 0;
while (et == ET_VLAN || et == ET_QINQ) {
if (++tags > LIM.vlan)
return { ok: false, err: ERR.VLAN };
off += VLAN_TAG;
if (off + 2 > len)
return { ok: false, err: ERR.SHORT };
et = u16(buf, off);
}
off += 2;
let af, proto;
if (et == ET_IPV4) {
if (off + IP4_MIN > len)
return { ok: false, err: ERR.SHORT };
let ihl = (ord(buf, off) & 0x0f) * 4;
if (ihl < IP4_MIN)
return { ok: false, err: ERR.IPLEN };
if (off + ihl > len)
return { ok: false, err: ERR.SHORT };
let tot = u16(buf, off + 2);
if (tot < ihl)
return { ok: false, err: ERR.IPLEN };
if (off + tot > len)
return { ok: false, err: ERR.SHORT };
len = off + tot;
if (u16(buf, off + 6) & IP4_FRAG_MASK)
return { ok: false, err: ERR.FRAG };
proto = ord(buf, off + 9);
af = 4;
off += ihl;
}
else if (et == ET_IPV6) {
if (off + IP6_HDR > len)
return { ok: false, err: ERR.SHORT };
let plen = u16(buf, off + 4);
if (off + IP6_HDR + plen > len)
return { ok: false, err: ERR.SHORT };
len = off + IP6_HDR + plen;
proto = ord(buf, off + 6);
af = 6;
off += IP6_HDR;
for (let i = 0; i < LIM.ext && is_ext(proto); i++) {
if (off + EXT_MIN > len)
return { ok: false, err: ERR.SHORT };
let hlen = (proto == IP_AH)
? (ord(buf, off + 1) + 2) * 4
: (ord(buf, off + 1) + 1) * 8;
proto = ord(buf, off);
off += hlen;
}
if (proto == IP_FRAG)
return { ok: false, err: ERR.FRAG };
if (is_ext(proto))
return { ok: false, err: ERR.EXTHDR };
}
else
return { ok: false, err: ERR.ETHER };
if (proto != IP_UDP)
return { ok: false, err: ERR.PROTO };
if (off + UDP_HDR > len)
return { ok: false, err: ERR.SHORT };
let sport = u16(buf, off);
let ulen = u16(buf, off + 4);
if (ulen < UDP_HDR || off + ulen > len)
return { ok: false, err: ERR.UDPLEN };
return {
ok: true,
af,
sport,
payload: substr(buf, off + UDP_HDR, ulen - UDP_HDR)
};
};
|