-
Notifications
You must be signed in to change notification settings - Fork 197
Expand file tree
/
Copy pathnfqueue_linux.go
More file actions
306 lines (280 loc) · 8.17 KB
/
Copy pathnfqueue_linux.go
File metadata and controls
306 lines (280 loc) · 8.17 KB
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
//go:build linux
package tun
import (
"context"
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-tun/gtcpip/header"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/florianl/go-nfqueue/v2"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
const nfqueueMaxPacketLen = 512
type nfqueueHandler struct {
ctx context.Context
cancel context.CancelFunc
handler Handler
logger logger.Logger
nfq *nfqueue.Nfqueue
queue uint16
outputMark uint32
resetMark uint32
closed atomic.Bool
}
type nfqueueOptions struct {
Context context.Context
Handler Handler
Logger logger.Logger
Queue uint16
OutputMark uint32
ResetMark uint32
}
func newNFQueueHandler(options nfqueueOptions) (*nfqueueHandler, error) {
ctx, cancel := context.WithCancel(options.Context)
return &nfqueueHandler{
ctx: ctx,
cancel: cancel,
handler: options.Handler,
logger: options.Logger,
queue: options.Queue,
outputMark: options.OutputMark,
resetMark: options.ResetMark,
}, nil
}
func (h *nfqueueHandler) setVerdict(packetID uint32, verdict int, mark uint32) {
var err error
if mark != 0 {
err = h.nfq.SetVerdictWithOption(packetID, verdict, nfqueue.WithMark(mark))
} else {
err = h.nfq.SetVerdict(packetID, verdict)
}
if err != nil && !h.closed.Load() && h.ctx.Err() == nil {
h.logger.Trace(E.Cause(err, "set verdict"))
}
}
func (h *nfqueueHandler) Start() error {
config := nfqueue.Config{
NfQueue: h.queue,
MaxPacketLen: nfqueueMaxPacketLen,
MaxQueueLen: 4096,
Copymode: nfqueue.NfQnlCopyPacket,
AfFamily: unix.AF_UNSPEC,
Flags: nfqueue.NfQaCfgFlagFailOpen,
}
nfq, err := nfqueue.Open(&config)
if err != nil {
return E.Cause(err, "open nfqueue")
}
if err = nfq.SetOption(netlink.NoENOBUFS, true); err != nil {
nfq.Close()
return E.Cause(err, "set nfqueue option")
}
err = nfq.RegisterWithErrorFunc(h.ctx, h.handlePacket, func(e error) int {
if h.ctx.Err() != nil {
return 1
}
h.logger.Error("nfqueue error: ", e)
return 0
})
if err != nil {
nfq.Close()
return E.Cause(err, "register nfqueue")
}
h.nfq = nfq
return nil
}
const ipv6AuthenticationHeaderIdentifier header.IPv6ExtensionHeaderIdentifier = 51
type preMatchPacket struct {
protocol uint8
source netip.AddrPort
destination netip.AddrPort
firstPacket []byte
}
func parsePreMatchPacket(packet []byte) (preMatchPacket, bool) {
if len(packet) < 1 {
return preMatchPacket{}, false
}
var (
protocol uint8
transportOffset int
source netip.Addr
destination netip.Addr
)
switch header.IPVersion(packet) {
case header.IPv4Version:
if len(packet) < header.IPv4MinimumSize {
return preMatchPacket{}, false
}
ipHdr := header.IPv4(packet)
transportOffset = int(ipHdr.HeaderLength())
if transportOffset < header.IPv4MinimumSize || transportOffset > len(packet) || int(ipHdr.TotalLength()) < transportOffset || ipHdr.FragmentOffset() != 0 {
return preMatchPacket{}, false
}
protocol = uint8(ipHdr.TransportProtocol())
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
case header.IPv6Version:
if len(packet) < header.IPv6MinimumSize {
return preMatchPacket{}, false
}
ipHdr := header.IPv6(packet)
var ok bool
protocol, transportOffset, ok = parsePreMatchIPv6Transport(packet)
if !ok {
return preMatchPacket{}, false
}
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
default:
return preMatchPacket{}, false
}
transport := packet[transportOffset:]
parsed := preMatchPacket{protocol: protocol}
switch protocol {
case uint8(header.TCPProtocolNumber):
if len(transport) < header.TCPMinimumSize {
return preMatchPacket{}, false
}
tcpHdr := header.TCP(transport)
flags := tcpHdr.Flags()
if !flags.Contains(header.TCPFlagSyn) || flags.Contains(header.TCPFlagAck) {
return preMatchPacket{}, false
}
parsed.source = netip.AddrPortFrom(source, tcpHdr.SourcePort())
parsed.destination = netip.AddrPortFrom(destination, tcpHdr.DestinationPort())
case uint8(header.UDPProtocolNumber):
if len(transport) < header.UDPMinimumSize {
return preMatchPacket{}, false
}
udpHdr := header.UDP(transport)
udpLength := int(udpHdr.Length())
if udpLength < header.UDPMinimumSize {
return preMatchPacket{}, false
}
if udpLength < len(transport) {
transport = transport[:udpLength]
}
parsed.source = netip.AddrPortFrom(source, udpHdr.SourcePort())
parsed.destination = netip.AddrPortFrom(destination, udpHdr.DestinationPort())
parsed.firstPacket = header.UDP(transport).Payload()
case uint8(header.ICMPv4ProtocolNumber):
if !source.Is4() || len(transport) < header.ICMPv4MinimumSize {
return preMatchPacket{}, false
}
icmpHdr := header.ICMPv4(transport)
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != 0 {
return preMatchPacket{}, false
}
identifier := icmpHdr.Ident()
parsed.source = netip.AddrPortFrom(source, identifier)
parsed.destination = netip.AddrPortFrom(destination, identifier)
case uint8(header.ICMPv6ProtocolNumber):
if !source.Is6() || len(transport) < header.ICMPv6MinimumSize {
return preMatchPacket{}, false
}
icmpHdr := header.ICMPv6(transport)
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != 0 {
return preMatchPacket{}, false
}
identifier := icmpHdr.Ident()
parsed.source = netip.AddrPortFrom(source, identifier)
parsed.destination = netip.AddrPortFrom(destination, identifier)
default:
return preMatchPacket{}, false
}
return parsed, true
}
func parsePreMatchIPv6Transport(packet []byte) (transportProto uint8, transportOffset int, ok bool) {
nextHeader := header.IPv6(packet).NextHeader()
offset := header.IPv6MinimumSize
for {
switch header.IPv6ExtensionHeaderIdentifier(nextHeader) {
case header.IPv6HopByHopOptionsExtHdrIdentifier,
header.IPv6RoutingExtHdrIdentifier,
header.IPv6DestinationOptionsExtHdrIdentifier:
if len(packet) < offset+2 {
return 0, 0, false
}
nextHeader = packet[offset]
extensionLength := (int(packet[offset+1]) + 1) * 8
if len(packet) < offset+extensionLength {
return 0, 0, false
}
offset += extensionLength
case header.IPv6FragmentExtHdrIdentifier:
if len(packet) < offset+header.IPv6FragmentHeaderSize {
return 0, 0, false
}
fragmentHdr := header.IPv6Fragment(packet[offset:])
if fragmentHdr.FragmentOffset() != 0 {
return 0, 0, false
}
nextHeader = fragmentHdr.NextHeader()
offset += header.IPv6FragmentHeaderSize
case ipv6AuthenticationHeaderIdentifier:
if len(packet) < offset+2 {
return 0, 0, false
}
nextHeader = packet[offset]
extensionLength := (int(packet[offset+1]) + 2) * 4
if len(packet) < offset+extensionLength {
return 0, 0, false
}
offset += extensionLength
case header.IPv6NoNextHeaderIdentifier:
return 0, 0, false
default:
return nextHeader, offset, true
}
}
}
func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
if h.closed.Load() {
return 0
}
if attr.PacketID == nil || attr.Payload == nil {
return 0
}
packetID := *attr.PacketID
payload := *attr.Payload
packet, loaded := parsePreMatchPacket(payload)
if !loaded {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
verdict := h.handler.JudgeFlow(
packet.protocol,
packet.source,
packet.destination,
packet.firstPacket,
)
// Use NfRepeat for bypass/reset so the packet re-enters the chain
// from the beginning, allowing mark-checking rules to save the mark
// to conntrack. NfAccept is a terminal verdict in nftables — it exits
// the chain immediately, skipping any rules after the queue statement.
switch verdict.Action {
case ActionBypass:
h.setVerdict(packetID, nfqueue.NfRepeat, h.outputMark)
case ActionReject:
if packet.protocol == uint8(unix.IPPROTO_TCP) {
h.setVerdict(packetID, nfqueue.NfRepeat, h.resetMark)
} else {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
}
case ActionDrop:
h.setVerdict(packetID, nfqueue.NfDrop, 0)
default:
h.setVerdict(packetID, nfqueue.NfAccept, 0)
}
return 0
}
func (h *nfqueueHandler) Close() error {
h.closed.Store(true)
h.cancel()
if h.nfq != nil {
h.nfq.Close()
}
return nil
}