Skip to content

Commit 909ab10

Browse files
committed
Fix nfqueue pre-match chain priorities and missing exclude rules
1 parent da24aca commit 909ab10

3 files changed

Lines changed: 134 additions & 100 deletions

File tree

nfqueue_linux.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,9 @@ func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
272272
packet.firstPacket,
273273
)
274274

275-
// Use NfRepeat for bypass/reset so the packet re-enters the chain
276-
// from the beginning, allowing mark-checking rules to save the mark
277-
// to conntrack. NfAccept is a terminal verdict in nftables — it exits
278-
// the chain immediately, skipping any rules after the queue statement.
275+
// nf_reinject: NF_REPEAT re-runs the queueing chain from its first rule,
276+
// while NF_ACCEPT continues at the next hook, skipping the remaining
277+
// rules of the chain.
279278
switch verdict.Action {
280279
case ActionBypass:
281280
h.setVerdict(packetID, nfqueue.NfRepeat, h.outputMark)

redirect_nftables.go

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (r *autoRedirect) setupNFTables() error {
6464
if !r.shouldSkipOutputChain() {
6565
outputNATPriority := nftables.ChainPriorityMangle
6666
if r.nfqueueEnabled {
67-
outputNATPriority = nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 1)
67+
outputNATPriority = nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 2)
6868
}
6969
chainOutput := nft.AddChain(&nftables.Chain{
7070
Name: "output",
@@ -88,7 +88,7 @@ func (r *autoRedirect) setupNFTables() error {
8888
Name: "output_route",
8989
Table: table,
9090
Hooknum: nftables.ChainHookOutput,
91-
Priority: nftables.ChainPriorityMangle,
91+
Priority: outputNATPriority,
9292
Type: nftables.ChainTypeRoute,
9393
})
9494
err = r.nftablesCreateLoopbackReroute(nft, table, chainOutputRoute)
@@ -100,7 +100,7 @@ func (r *autoRedirect) setupNFTables() error {
100100
Name: "output_udp_icmp",
101101
Table: table,
102102
Hooknum: nftables.ChainHookOutput,
103-
Priority: nftables.ChainPriorityMangle,
103+
Priority: outputNATPriority,
104104
Type: nftables.ChainTypeRoute,
105105
})
106106
err = r.nftablesCreateExcludeRules(nft, table, chainOutputUDP)
@@ -135,11 +135,17 @@ func (r *autoRedirect) setupNFTables() error {
135135
r.nftablesCreateRedirectPortReject(nft, table, chainInput)
136136
}
137137

138+
preroutingNATPriority := nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1)
139+
preroutingRoutePriority := nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 2)
140+
if r.nfqueueEnabled {
141+
preroutingNATPriority = nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 2)
142+
preroutingRoutePriority = nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 3)
143+
}
138144
chainPreRouting := nft.AddChain(&nftables.Chain{
139145
Name: "prerouting",
140146
Table: table,
141147
Hooknum: nftables.ChainHookPrerouting,
142-
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1),
148+
Priority: preroutingNATPriority,
143149
Type: nftables.ChainTypeNAT,
144150
})
145151
err = r.nftablesCreateExcludeRules(nft, table, chainPreRouting)
@@ -158,7 +164,7 @@ func (r *autoRedirect) setupNFTables() error {
158164
Name: "prerouting_filter",
159165
Table: table,
160166
Hooknum: nftables.ChainHookPrerouting,
161-
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1),
167+
Priority: preroutingNATPriority,
162168
Type: nftables.ChainTypeFilter,
163169
})
164170
err = r.nftablesCreateLoopbackReroute(nft, table, chainPreRoutingFilter)
@@ -170,7 +176,7 @@ func (r *autoRedirect) setupNFTables() error {
170176
Name: "prerouting_udp_icmp",
171177
Table: table,
172178
Hooknum: nftables.ChainHookPrerouting,
173-
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 2),
179+
Priority: preroutingRoutePriority,
174180
Type: nftables.ChainTypeFilter,
175181
})
176182
ipProto := &nftables.Set{
@@ -331,7 +337,6 @@ func (r *autoRedirect) updateNetworkAddresses() error {
331337
return err
332338
}
333339

334-
// TODO: test if this works
335340
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
336341
err := r.interfaceFinder.Update()
337342
if err != nil {
@@ -414,20 +419,20 @@ func (r *autoRedirect) nftablesCreatePreMatchChains(nft *nftables.Conn, table *n
414419
Name: "prerouting_prematch",
415420
Table: table,
416421
Hooknum: nftables.ChainHookPrerouting,
417-
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest - 1),
422+
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1),
418423
Type: nftables.ChainTypeFilter,
419424
})
420425
err := r.nftablesAddPreMatchRules(nft, table, chainPreroutingPreMatch, true)
421426
if err != nil {
422427
return err
423428
}
424429

425-
if !r.shouldSkipOutputChain() {
430+
if r.tunOptions.AutoRedirectMarkMode && !r.shouldSkipOutputChain() {
426431
chainOutputPreMatch := nft.AddChain(&nftables.Chain{
427432
Name: "output_prematch",
428433
Table: table,
429434
Hooknum: nftables.ChainHookOutput,
430-
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityMangle - 1),
435+
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 1),
431436
Type: nftables.ChainTypeFilter,
432437
})
433438
err = r.nftablesAddPreMatchRules(nft, table, chainOutputPreMatch, false)
@@ -440,19 +445,33 @@ func (r *autoRedirect) nftablesCreatePreMatchChains(nft *nftables.Conn, table *n
440445
}
441446

442447
func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain, isPrerouting bool) error {
443-
ifnameKey := expr.MetaKeyOIFNAME
444-
if isPrerouting {
445-
ifnameKey = expr.MetaKeyIIFNAME
448+
if !isPrerouting {
449+
nft.AddRule(&nftables.Rule{
450+
Table: table,
451+
Chain: chain,
452+
Exprs: []expr.Any{
453+
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
454+
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: nftablesIfname(r.tunOptions.Name)},
455+
&expr.Verdict{Kind: expr.VerdictReturn},
456+
},
457+
})
458+
}
459+
460+
if r.enableIPv4 != r.enableIPv6 {
461+
disabledFamily := nftables.TableFamilyIPv6
462+
if r.enableIPv6 {
463+
disabledFamily = nftables.TableFamilyIPv4
464+
}
465+
nft.AddRule(&nftables.Rule{
466+
Table: table,
467+
Chain: chain,
468+
Exprs: []expr.Any{
469+
&expr.Meta{Key: expr.MetaKeyNFPROTO, Register: 1},
470+
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{uint8(disabledFamily)}},
471+
&expr.Verdict{Kind: expr.VerdictReturn},
472+
},
473+
})
446474
}
447-
nft.AddRule(&nftables.Rule{
448-
Table: table,
449-
Chain: chain,
450-
Exprs: []expr.Any{
451-
&expr.Meta{Key: ifnameKey, Register: 1},
452-
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: nftablesIfname(r.tunOptions.Name)},
453-
&expr.Verdict{Kind: expr.VerdictReturn},
454-
},
455-
})
456475

457476
preMatchProtocols := &nftables.Set{
458477
Table: table,
@@ -487,12 +506,31 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab
487506
},
488507
})
489508

490-
// Bypass mark: save to conntrack and return.
491-
// When the NFQUEUE handler returns NF_REPEAT with the output mark,
492-
// the packet re-enters this chain from the beginning. This rule
493-
// catches it, saves the mark to conntrack (so subsequent packets
494-
// of the same connection are bypassed via ct mark check below),
495-
// and returns.
509+
nft.AddRule(&nftables.Rule{
510+
Table: table,
511+
Chain: chain,
512+
Exprs: []expr.Any{
513+
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
514+
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
515+
&expr.Payload{
516+
OperationType: expr.PayloadLoad,
517+
DestRegister: 1,
518+
Base: expr.PayloadBaseTransportHeader,
519+
Offset: 13,
520+
Len: 1,
521+
},
522+
&expr.Bitwise{
523+
SourceRegister: 1,
524+
DestRegister: 1,
525+
Len: 1,
526+
Mask: []byte{0x12},
527+
Xor: []byte{0x00},
528+
},
529+
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0x02}},
530+
&expr.Verdict{Kind: expr.VerdictReturn},
531+
},
532+
})
533+
496534
nft.AddRule(&nftables.Rule{
497535
Table: table,
498536
Chain: chain,
@@ -505,9 +543,6 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab
505543
},
506544
})
507545

508-
// Reset mark: reject with TCP RST.
509-
// When the NFQUEUE handler returns NF_REPEAT with the reset mark,
510-
// the packet re-enters this chain and is rejected here.
511546
nft.AddRule(&nftables.Rule{
512547
Table: table,
513548
Chain: chain,
@@ -521,7 +556,6 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab
521556
},
522557
})
523558

524-
// Already-tracked bypass connections: return immediately.
525559
nft.AddRule(&nftables.Rule{
526560
Table: table,
527561
Chain: chain,
@@ -544,35 +578,24 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab
544578
})
545579
}
546580

581+
err = r.nftablesCreateExcludeRules(nft, table, chain)
582+
if err != nil {
583+
return err
584+
}
585+
547586
queueExpression := func() *expr.Queue {
548587
return &expr.Queue{
549588
Num: r.effectiveNFQueue(),
550589
Flag: expr.QueueFlagBypass,
551590
}
552591
}
553592

554-
// TCP SYN: send to NFQUEUE for pre-match evaluation.
555593
nft.AddRule(&nftables.Rule{
556594
Table: table,
557595
Chain: chain,
558596
Exprs: []expr.Any{
559597
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
560598
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
561-
&expr.Payload{
562-
OperationType: expr.PayloadLoad,
563-
DestRegister: 1,
564-
Base: expr.PayloadBaseTransportHeader,
565-
Offset: 13,
566-
Len: 1,
567-
},
568-
&expr.Bitwise{
569-
SourceRegister: 1,
570-
DestRegister: 1,
571-
Len: 1,
572-
Mask: []byte{0x12},
573-
Xor: []byte{0x00},
574-
},
575-
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{0x02}},
576599
&expr.Counter{},
577600
queueExpression(),
578601
},

redirect_nftables_rules.go

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (r *autoRedirect) nftablesCreateLoopbackAddressSets(
132132
}
133133

134134
func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) error {
135-
if r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput {
135+
if r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type != nftables.ChainTypeFilter {
136136
if chain.Type == nftables.ChainTypeRoute {
137137
ipProto := &nftables.Set{
138138
Table: table,
@@ -675,8 +675,10 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
675675
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false)
676676
}
677677

678-
if r.tunOptions.DNSModeOrDefault() == DNSModeHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) ||
679-
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) {
678+
if r.tunOptions.DNSModeOrDefault() == DNSModeHijack &&
679+
(chain.Type == nftables.ChainTypeNAT || chain.Type == nftables.ChainTypeFilter) &&
680+
(chain.Hooknum == nftables.ChainHookPrerouting ||
681+
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput)) {
680682
if r.enableIPv4 {
681683
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4, 5, "inet4_local_address_set")
682684
if err != nil {
@@ -717,42 +719,44 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
717719
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false)
718720
}
719721

720-
mptcpVerdict := expr.VerdictDrop
721-
if r.tunOptions.ExcludeMPTCP {
722-
mptcpVerdict = expr.VerdictReturn
723-
}
724-
nft.AddRule(&nftables.Rule{
725-
Table: table,
726-
Chain: chain,
727-
Exprs: []expr.Any{
728-
&expr.Meta{
729-
Key: expr.MetaKeyL4PROTO,
730-
Register: 1,
731-
},
732-
&expr.Cmp{
733-
Op: expr.CmpOpEq,
734-
Register: 1,
735-
Data: []byte{unix.IPPROTO_TCP},
736-
},
737-
&expr.Exthdr{
738-
DestRegister: 1,
739-
Type: 30,
740-
Offset: 0,
741-
Len: 1,
742-
Flags: unix.NFT_EXTHDR_F_PRESENT,
743-
Op: expr.ExthdrOpTcpopt,
744-
},
745-
&expr.Cmp{
746-
Op: expr.CmpOpEq,
747-
Register: 1,
748-
Data: []byte{1},
749-
},
750-
&expr.Counter{},
751-
&expr.Verdict{
752-
Kind: mptcpVerdict,
722+
if chain.Type == nftables.ChainTypeNAT || (chain.Type == nftables.ChainTypeFilter && r.tunOptions.ExcludeMPTCP) {
723+
mptcpVerdict := expr.VerdictDrop
724+
if r.tunOptions.ExcludeMPTCP {
725+
mptcpVerdict = expr.VerdictReturn
726+
}
727+
nft.AddRule(&nftables.Rule{
728+
Table: table,
729+
Chain: chain,
730+
Exprs: []expr.Any{
731+
&expr.Meta{
732+
Key: expr.MetaKeyL4PROTO,
733+
Register: 1,
734+
},
735+
&expr.Cmp{
736+
Op: expr.CmpOpEq,
737+
Register: 1,
738+
Data: []byte{unix.IPPROTO_TCP},
739+
},
740+
&expr.Exthdr{
741+
DestRegister: 1,
742+
Type: 30,
743+
Offset: 0,
744+
Len: 1,
745+
Flags: unix.NFT_EXTHDR_F_PRESENT,
746+
Op: expr.ExthdrOpTcpopt,
747+
},
748+
&expr.Cmp{
749+
Op: expr.CmpOpEq,
750+
Register: 1,
751+
Data: []byte{1},
752+
},
753+
&expr.Counter{},
754+
&expr.Verdict{
755+
Kind: mptcpVerdict,
756+
},
753757
},
754-
},
755-
})
758+
})
759+
}
756760

757761
return nil
758762
}
@@ -827,7 +831,7 @@ func (r *autoRedirect) nftablesCreateMark(nft *nftables.Conn, table *nftables.Ta
827831
&expr.Meta{
828832
Key: expr.MetaKeyMARK,
829833
Register: 1,
830-
}, // output meta mark set myMark ct mark set meta mark
834+
},
831835
&expr.Ct{
832836
Key: expr.CtKeyMARK,
833837
Register: 1,
@@ -1140,16 +1144,24 @@ func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
11401144
Data: binaryutil.BigEndian.PutUint16(53),
11411145
},
11421146
&expr.Counter{},
1143-
&expr.Immediate{
1144-
Register: 1,
1145-
Data: dnsServer.AsSlice(),
1146-
},
1147-
&expr.NAT{
1148-
Type: expr.NATTypeDestNAT,
1149-
Family: uint32(family),
1150-
RegAddrMin: 1,
1151-
},
11521147
)
1148+
if chain.Type == nftables.ChainTypeFilter {
1149+
exprs = append(exprs, &expr.Verdict{
1150+
Kind: expr.VerdictReturn,
1151+
})
1152+
} else {
1153+
exprs = append(exprs,
1154+
&expr.Immediate{
1155+
Register: 1,
1156+
Data: dnsServer.AsSlice(),
1157+
},
1158+
&expr.NAT{
1159+
Type: expr.NATTypeDestNAT,
1160+
Family: uint32(family),
1161+
RegAddrMin: 1,
1162+
},
1163+
)
1164+
}
11531165
nft.AddRule(&nftables.Rule{
11541166
Table: table,
11551167
Chain: chain,

0 commit comments

Comments
 (0)