[PATCH v2 0/1] nf_conntrack_ftp: Added nfct_seqadj_ext_add().

Andrii Melnychenko
2025-10-16T10:48:07Z

There is an issue with FTP SNAT/DNAT. When the PASV/EPSV message is altered
The sequence adjustment is required, and there is an issue that seqadj is
not set up at that moment.

The easiest way to reproduce this issue is with PASV mode.
Topoloy:
```
+-------------------+ +----------------------------------+
| FTP: 192.168.13.2 | <-> | NAT: 192.168.13.3, 192.168.100.1 |
+-------------------+ +----------------------------------+
|
+-----------------------+
| Client: 192.168.100.2 |
+-----------------------+
```

nft ruleset:
```
nft flush ruleset
sudo nft add table inet ftp_nat
sudo nft add ct helper inet ftp_nat ftp_helper { type \"ftp\" protocol tcp\; }
sudo nft add chain inet ftp_nat prerouting { type filter hook prerouting priority 0 \; policy accept \; }
sudo nft add rule inet ftp_nat prerouting tcp dport 21 ct state new ct helper set "ftp_helper"
nft add table ip nat
nft add chain ip nat prerouting { type nat hook prerouting priority dstnat \; policy accept \; }
nft add chain ip nat postrouting { type nat hook postrouting priority srcnat \; policy accept \; }
nft add rule ip nat prerouting tcp dport 21 dnat ip prefix to ip daddr map { 192.168.100.1 : 192.168.13.2/32 }
nft add rule ip nat postrouting tcp sport 21 snat ip prefix to ip saddr map { 192.168.13.2 : 192.168.100.1/32 }

# nft -s list ruleset
table inet ftp_nat {
ct helper ftp_helper {
type "ftp" protocol tcp
l3proto inet
}

chain prerouting {
type filter hook prerouting priority filter; policy accept;
tcp dport 21 ct state new ct helper set "ftp_helper"
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
tcp dport 21 dnat ip prefix to ip daddr map { 192.168.100.1 : 192.168.13.2/32 }
}

chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
tcp sport 21 snat ip prefix to ip saddr map { 192.168.13.2 : 192.168.100.1/32 }
}
}

```

Connecting the client:
```
# ftp 192.168.100.1
Connected to 192.168.100.1.
220 Welcome to my FTP server.
Name (192.168.100.1:dev): user
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> epsv
EPSV/EPRT on IPv4 off.
EPSV/EPRT on IPv6 off.
ftp> ls
227 Entering passive mode (192,168,100,1,209,129).
421 Service not available, remote server has closed connection.
```

Kernel logs:
```
Oct 16 10:24:37 vyos kernel: nf_conntrack_ftp: ftp: Conntrackinfo = 2
Oct 16 10:24:37 vyos kernel: nf_conntrack_ftp: ftp: dataoff(60) >= skblen(60)
Oct 16 10:24:37 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:37 vyos kernel: nf_conntrack_ftp: nf_conntrack_ftp: wrong seq pos (UNSET)(0) or (UNSET)(0)
Oct 16 10:24:37 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:38 vyos kernel: nf_conntrack_ftp: nf_conntrack_ftp: wrong seq pos (UNSET)(0) or (UNSET)(0)
Oct 16 10:24:38 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:38 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 33
Oct 16 10:24:38 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 33
Oct 16 10:24:38 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `PORT': dlen = 8
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `EPRT': dlen = 8
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 23
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 23
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `PORT': dlen = 6
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `EPRT': dlen = 6
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 19
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 19
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `PORT': dlen = 6
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `EPRT': dlen = 6
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 25
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 25
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 133
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 133
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 15
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: find_pattern `229 ': dlen = 15
Oct 16 10:24:40 vyos kernel: nf_conntrack_ftp: ftp: dataoff(52) >= skblen(52)
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: find_pattern `PORT': dlen = 6
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: find_pattern `EPRT': dlen = 6
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 51
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: Pattern matches!
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: Skipped up to 0x0 delimiter!
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: Match succeeded!
Oct 16 10:24:44 vyos kernel: nf_conntrack_ftp: conntrack_ftp: match `192,168,13,2,209,129' (20 bytes at 2149072380)
Oct 16 10:24:44 vyos kernel: ------------[ cut here ]------------
Oct 16 10:24:44 vyos kernel: Missing nfct_seqadj_ext_add() setup call
Oct 16 10:24:44 vyos kernel: WARNING: CPU: 1 PID: 0 at net/netfilter/nf_conntrack_seqadj.c:41 nf_ct_seqadj_set+0xbf/0xe0 [nf_conntrack]
Oct 16 10:24:44 vyos kernel: Modules linked in: nf_nat_ftp(E) nft_nat(E) nf_conntrack_ftp(E) af_packet(E) nft_ct(E) nft_chain_nat(E) nf_nat(E) nf_tables(E) nfnetlink_cthelper(E) nf_conntrack(E) nf_defrag_ipv6(E) nf_defrag_ipv4(E) nfnetlink(E) binfmt_misc(E) intel_rapl_common(E) crct10dif_pclmul(E) crc32_pclmul(E) ghash_clmulni_intel(E) sha512_ssse3(E) sha256_ssse3(E) sha1_ssse3(E) aesni_intel(E) crypto_simd(E) cryptd(E) rapl(E) iTCO_wdt(E) iTCO_vendor_support(E) button(E) virtio_console(E) virtio_balloon(E) pcspkr(E) evdev(E) tcp_bbr(E) sch_fq_codel(E) mpls_iptunnel(E) mpls_router(E) ip_tunnel(E) br_netfilter(E) bridge(E) stp(E) llc(E) fuse(E) efi_pstore(E) configfs(E) virtio_rng(E) rng_core(E) ip_tables(E) x_tables(E) autofs4(E) usb_storage(E) ohci_hcd(E) uhci_hcd(E) ehci_hcd(E) sd_mod(E) squashfs(E) lz4_decompress(E) loop(E) overlay(E) ext4(E) crc16(E) mbcache(E) jbd2(E) nls_cp437(E) vfat(E) fat(E) efivarfs(E) nls_ascii(E) hid_generic(E) usbhid(E) hid(E) virtio_net(E) net_failover(E) virtio_blk(E) failover(E) ahci(E) libahci(E)
Oct 16 10:24:44 vyos kernel: crc32c_intel(E) i2c_i801(E) i2c_smbus(E) libata(E) lpc_ich(E) scsi_mod(E) scsi_common(E) xhci_pci(E) xhci_hcd(E) virtio_pci(E) virtio_pci_legacy_dev(E) virtio_pci_modern_dev(E) virtio(E) virtio_ring(E)
Oct 16 10:24:44 vyos kernel: CPU: 1 PID: 0 Comm: swapper/1 Tainted: G E 6.6.108-vyos #1
Oct 16 10:24:44 vyos kernel: Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS Arch Linux 1.17.0-2-2 04/01/2014
Oct 16 10:24:44 vyos kernel: RIP: 0010:nf_ct_seqadj_set+0xbf/0xe0 [nf_conntrack]
Oct 16 10:24:44 vyos kernel: Code: ea 44 89 20 89 50 08 eb db 45 85 ed 74 de 80 3d 51 6d 00 00 00 75 d5 48 c7 c7 68 57 ad c0 c6 05 41 6d 00 00 01 e8 71 28 dd dc <0f> 0b eb be be 02 00 00 00 e8 63 fc ff ff 48 89 c3 e9 66 ff ff ff
Oct 16 10:24:44 vyos kernel: RSP: 0018:ffff9a66c00e8910 EFLAGS: 00010286
Oct 16 10:24:44 vyos kernel: RAX: 0000000000000000 RBX: 0000000000000014 RCX: 000000000000083f
Oct 16 10:24:44 vyos kernel: RDX: 0000000000000000 RSI: 00000000000000f6 RDI: 000000000000083f
Oct 16 10:24:44 vyos kernel: RBP: ffff89387978fb00 R08: 0000000000000000 R09: ffff9a66c00e87a8
Oct 16 10:24:44 vyos kernel: R10: 0000000000000003 R11: ffffffff9ecbab08 R12: ffff89387978fb00
Oct 16 10:24:44 vyos kernel: R13: 0000000000000001 R14: ffff893872e18862 R15: ffff893842f8c700
Oct 16 10:24:44 vyos kernel: FS: 0000000000000000(0000) GS:ffff893bafc80000(0000) knlGS:0000000000000000
Oct 16 10:24:44 vyos kernel: CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
Oct 16 10:24:44 vyos kernel: CR2: 000055fbc64ec690 CR3: 000000011de22001 CR4: 0000000000370ee0
Oct 16 10:24:44 vyos kernel: Call Trace:
Oct 16 10:24:44 vyos kernel: <IRQ>
Oct 16 10:24:44 vyos kernel: __nf_nat_mangle_tcp_packet+0x100/0x160 [nf_nat]
Oct 16 10:24:44 vyos kernel: nf_nat_ftp+0x142/0x280 [nf_nat_ftp]
Oct 16 10:24:44 vyos kernel: ? kmem_cache_alloc+0x157/0x290
Oct 16 10:24:44 vyos kernel: ? help+0x4d1/0x880 [nf_conntrack_ftp]
Oct 16 10:24:44 vyos kernel: help+0x4d1/0x880 [nf_conntrack_ftp]
Oct 16 10:24:44 vyos kernel: ? nf_confirm+0x122/0x2e0 [nf_conntrack]
Oct 16 10:24:44 vyos kernel: nf_confirm+0x122/0x2e0 [nf_conntrack]
Oct 16 10:24:44 vyos kernel: nf_hook_slow+0x3c/0xb0
Oct 16 10:24:44 vyos kernel: ip_output+0xb6/0xf0
Oct 16 10:24:44 vyos kernel: ? __pfx_ip_finish_output+0x10/0x10
Oct 16 10:24:44 vyos kernel: ip_sublist_rcv_finish+0x90/0xa0
Oct 16 10:24:44 vyos kernel: ip_sublist_rcv+0x190/0x220
Oct 16 10:24:44 vyos kernel: ? __pfx_ip_rcv_finish+0x10/0x10
Oct 16 10:24:44 vyos kernel: ip_list_rcv+0x134/0x160
Oct 16 10:24:44 vyos kernel: __netif_receive_skb_list_core+0x299/0x2c0
Oct 16 10:24:44 vyos kernel: netif_receive_skb_list_internal+0x1a7/0x2d0
Oct 16 10:24:44 vyos kernel: napi_complete_done+0x69/0x1a0
Oct 16 10:24:44 vyos kernel: virtnet_poll+0x3c0/0x540 [virtio_net]
Oct 16 10:24:44 vyos kernel: __napi_poll+0x26/0x1a0
Oct 16 10:24:44 vyos kernel: net_rx_action+0x141/0x2c0
Oct 16 10:24:44 vyos kernel: ? lock_timer_base+0x5c/0x80
Oct 16 10:24:44 vyos kernel: handle_softirqs+0xd5/0x280
Oct 16 10:24:44 vyos kernel: __irq_exit_rcu+0x95/0xb0
Oct 16 10:24:44 vyos kernel: common_interrupt+0x7a/0xa0
Oct 16 10:24:44 vyos kernel: </IRQ>
Oct 16 10:24:44 vyos kernel: <TASK>
Oct 16 10:24:44 vyos kernel: asm_common_interrupt+0x22/0x40
Oct 16 10:24:44 vyos kernel: RIP: 0010:pv_native_safe_halt+0xb/0x10
Oct 16 10:24:44 vyos kernel: Code: 0b 66 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 66 90 0f 00 2d 29 9a 3e 00 fb f4 <c3> cc cc cc cc 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 8b
Oct 16 10:24:44 vyos kernel: RSP: 0018:ffff9a66c009bed8 EFLAGS: 00000252
Oct 16 10:24:44 vyos kernel: RAX: ffff893bafcaaca8 RBX: 0000000000000001 RCX: 0000000000000001
Oct 16 10:24:44 vyos kernel: RDX: 0000000000000000 RSI: 0000000000000083 RDI: 0000000000064cec
Oct 16 10:24:44 vyos kernel: RBP: ffff8938401f2200 R08: 0000000000000001 R09: 0000000000000000
Oct 16 10:24:44 vyos kernel: R10: 000000000001ffc0 R11: 0000000000000000 R12: 0000000000000000
Oct 16 10:24:44 vyos kernel: R13: 0000000000000000 R14: ffff8938401f2200 R15: 0000000000000000
Oct 16 10:24:44 vyos kernel: default_idle+0x5/0x20
Oct 16 10:24:44 vyos kernel: default_idle_call+0x28/0xb0
Oct 16 10:24:44 vyos kernel: do_idle+0x1ec/0x230
Oct 16 10:24:44 vyos kernel: cpu_startup_entry+0x21/0x30
Oct 16 10:24:44 vyos kernel: start_secondary+0x11a/0x140
Oct 16 10:24:44 vyos kernel: secondary_startup_64_no_verify+0x178/0x17b
Oct 16 10:24:44 vyos kernel: </TASK>
Oct 16 10:24:44 vyos kernel: ---[ end trace 0000000000000000 ]---
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: find_pattern `227 ': dlen = 51
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: Pattern matches!
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: Skipped up to 0x0 delimiter!
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: Match succeeded!
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: conntrack_ftp: match `192,168,13,2,209,129' (20 bytes at 2149072380)
Oct 16 10:24:45 vyos kernel: nf_conntrack_ftp: ftp: dataoff(40) >= skblen(40)
```

According to callstack, despite installing nf_nat_follow_master() helper,
the nfct_seqadj() call comes almost immediately after, before any
potential setups on already confirmed conntrack.

```
net/netfilter/nf_conntrack_proto.c: nf_confirm()

net/netfilter/nf_conntrack_ftp.c: help()
nf_ct_expect_init()
nf_nat_ftp()

net/netfilter/nf_nat_ftp.c: nf_nat_ftp()
exp->expectfn = nf_nat_follow_master;
nf_nat_mangle_tcp_packet()

net/netfilter/nf_nat_helper.c: __nf_nat_mangle_tcp_packet()
nf_ct_seqadj_set()

net/netfilter/nf_conntrack_seqadj.c: nf_ct_seqadj_set()
if (unlikely(!seqadj)) {
WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n");
return 0;
}
```

Andrii Melnychenko (1):
nf_conntrack_ftp: Added nfct_seqadj_ext_add() for ftp's conntrack.

net/netfilter/nf_conntrack_ftp.c | 3 +++
1 file changed, 3 insertions(+)

--
2.43.0