diff --git a/nfqueue_linux.go b/nfqueue_linux.go index e52cd46..baaefb5 100644 --- a/nfqueue_linux.go +++ b/nfqueue_linux.go @@ -214,11 +214,15 @@ func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int { _, pErr := h.handler.PrepareConnection(N.NetworkTCP, srcAddr, dstAddr, nil, 0) + // 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 { case errors.Is(pErr, ErrBypass): - h.setVerdict(packetID, nfqueue.NfAccept, h.outputMark) + h.setVerdict(packetID, nfqueue.NfRepeat, h.outputMark) case errors.Is(pErr, ErrReset): - h.setVerdict(packetID, nfqueue.NfAccept, h.resetMark) + h.setVerdict(packetID, nfqueue.NfRepeat, h.resetMark) case errors.Is(pErr, ErrDrop): h.setVerdict(packetID, nfqueue.NfDrop, 0) default: diff --git a/redirect_nftables.go b/redirect_nftables.go index f45f5df..266bbe9 100644 --- a/redirect_nftables.go +++ b/redirect_nftables.go @@ -423,16 +423,39 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab }, }) + // Bypass mark: save to conntrack and return. + // When the NFQUEUE handler returns NF_REPEAT with the output mark, + // the packet re-enters this chain from the beginning. This rule + // catches it, saves the mark to conntrack (so subsequent packets + // of the same connection are bypassed via ct mark check below), + // and returns. nft.AddRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ &expr.Meta{Key: expr.MetaKeyMARK, Register: 1}, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())}, + &expr.Ct{Key: expr.CtKeyMARK, Register: 1, SourceRegister: true}, + &expr.Counter{}, &expr.Verdict{Kind: expr.VerdictReturn}, }, }) + // Reset mark: reject with TCP RST. + // When the NFQUEUE handler returns NF_REPEAT with the reset mark, + // the packet re-enters this chain and is rejected here. + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyMARK, Register: 1}, + &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveResetMark())}, + &expr.Counter{}, + &expr.Reject{Type: unix.NFT_REJECT_TCP_RST}, + }, + }) + + // Already-tracked bypass connections: return immediately. nft.AddRule(&nftables.Rule{ Table: table, Chain: chain, @@ -443,6 +466,7 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab }, }) + // TCP SYN: send to NFQUEUE for pre-match evaluation. nft.AddRule(&nftables.Rule{ Table: table, Chain: chain, @@ -469,26 +493,4 @@ func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftab }, }, }) - - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chain, - Exprs: []expr.Any{ - &expr.Meta{Key: expr.MetaKeyMARK, Register: 1}, - &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveResetMark())}, - &expr.Counter{}, - &expr.Reject{Type: unix.NFT_REJECT_TCP_RST}, - }, - }) - - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chain, - Exprs: []expr.Any{ - &expr.Meta{Key: expr.MetaKeyMARK, Register: 1}, - &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())}, - &expr.Ct{Key: expr.CtKeyMARK, Register: 1, SourceRegister: true}, - &expr.Counter{}, - }, - }) }