2

I'm looking for a way to examine the first packet only of a newly established TCP connection (the first packet with actual payload, that is). Is there a way to do this with iptables? Matching ESTABLISHED packets would match all packets of a connection after handshake, right?

3 Answers 3

4

You can achieve your goal using (abusing) iptables, to be more specific: connbytes match and NFQUEUE target. connbytes allows you to match the Nth packet in the connection and NFQUEUE is a mechanism for passing packets matching an iptables rule to userspace program. Furthermore: you'll have to use some program which whill be receiving relevant packets from the kernel and processing them.

iptables

I'm assuming here that you are interested in capturing the packets server-side (that can be changed if you are interested in client-side capturing). In that case we'll need to capture the 3-rd incoming packet for each connection (i.e. the first incoming packet after the three-way handshake) and put the packet in a netfilter queue (queue #1 in this case).

iptables -I INPUT -p tcp -m tcp --dport 12345 -m connbytes --connbytes-mode packets --connbytes-dir original --connbytes 3:3 -j NFQUEUE --queue-num 1

As soon as a packet matches this rule, it will be passed to the userspace program bound to the queue #1. The program can then examine the packet and afterwards decide to accept it or drop it.

The program

You will need a program which will receive the packets in userspace using the libnetfilter_queue library. Bindings for the library are available in different languages. The following is a sample program written in python:

import struct

from netfilterqueue import NetfilterQueue

def ip_to_string(ip):
        return ".".join(map(lambda n: str(ip>>n & 0xff), [24,16,8,0]))

def print_and_accept(pkt):
        pl = pkt.get_payload()
        src_ip = struct.unpack('>I', pl[12:16])[0]
        tcp_offset = (struct.unpack('>B', pl[0:1])[0] & 0xf) * 4
        tmp = struct.unpack('>B', pl[tcp_offset+12:tcp_offset+13])[0]
        data_offset = ((tmp & 0xf0) >> 4) * 4
        src_port = struct.unpack('>H', pl[tcp_offset+0:tcp_offset+2])[0]
        data = pl[tcp_offset + data_offset:]
        print 'from {}:{}, "{}"'.format(ip_to_string(src_ip), src_port, data)
        pkt.accept()

nfqueue = NetfilterQueue()
nfqueue.bind(1, print_and_accept)
try:
        nfqueue.run()
except KeyboardInterrupt:
        print

The program assumes that the queued packets will be IPv4 TCP packets and prints the source ip:port pair and the TCP payload of the packet.

Caveats

  1. You can never be sure that the first data packet will have the complete TLS client hello - TCP can fragment the stream as it likes and it is not impossible to receive a single byte in the first data packet.
  2. TCP Fast Open will break the logic of this approach. If it is enabled, the initial three-way handshake may already transfer data. But TFO is disabled by default on almost every device for now.
  3. Care must be taken when using NFQUEUE target: if the userspace program bound to the queue hangs, crashes or is slow to process packets, those will be dropped/stuck and the service bound to the specified port will become unreachable. You can pass the --queue-bypass option to the NFQUEUE target to ACCEPT the matched packets if no userspace program is bound to the specified queue: this should help with the problem of the program crashing but won't help with a hung or slow program.
2

You can't do what you're looking for with stock iptables. You'd need to write some layer 7 inspection code.

An alternative, if you're willing to put up with some post processing, would be to capture traffic with tcpdump into PCAP files, parse them for the packets you're looking for, and throw away the rest. I know that Wireshark's SSL dissector and a display filter could get what you're looking for.

If you didn't mind that your captures would lag behind realtime a bit you could easily use tcpdump to capture the traffic to rotating PCAP files. You could run a script that monitors for completed PCAP files and parses them with tshark and a display filter to write out only what you're looking for to new PCAP files.

2

Matching ESTABLISHED packets would match all packets of a connection after handshake, right?

Right !

Not sure what you mean by "capture". iptables is a thing, network packet capture (tcpdump) is another thing.

Assuming that my understanding is "log only new connections", you will have to log only rules matching the NEW state.

Let's use a sample case where we want to log only new connections for an icmp (ping) request :

# Define LOG settings
iptables -N LOG_ACCEPT
iptables -A LOG_ACCEPT -j LOG --log-prefix '[IPTABLES ACCEPT] :'
iptables -A LOG_ACCEPT -j ACCEPT

iptables -A INPUT -p icmp -d 192.168.0.47 -m state --state NEW -j LOG_ACCEPT
iptables -A INPUT -p icmp -d 192.168.0.47 -m state --state ESTABLISHED,RELATED -j ACCEPT

Here we just LOG when NEW connection is established. So we can send 1000+ ping (from the same source host), only the first packet will be logged :

Apr  4 21:28:17 UBN-1 kernel: [78512.613705] [IPTABLES ACCEPT] :IN=eth0 OUT= MAC=00:0c:29:e4:d1:06:10:60:4b:69:7e:fd:08:00 SRC=192.168.0.39 DST=192.168.0.47 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=32571 PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=47088

If you put LOG_ACCEPT in the ESTABLISHED,RELATED rule :

iptables -A INPUT -p icmp -d 192.168.0.47 -m state --state NEW -j LOG_ACCEPT
iptables -A INPUT -p icmp -d 192.168.0.47 -m state --state ESTABLISHED,RELATED -j LOG_ACCEPT

then each ping request will be logged. So 1000+ ping (from the same source host) will generate 1000+ entries in the log :

Apr  4 21:33:49 UBN-1 kernel: [78844.130615] [IPTABLES ACCEPT] :IN=eth0 OUT= MAC=00:0c:29:e4:d1:06:10:60:4b:69:7e:fd:08:00 SRC=192.168.0.39 DST=192.168.0.47 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=244 PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=47092
Apr  4 21:33:50 UBN-1 kernel: [78845.130551] [IPTABLES ACCEPT] :IN=eth0 OUT= MAC=00:0c:29:e4:d1:06:10:60:4b:69:7e:fd:08:00 SRC=192.168.0.39 DST=192.168.0.47 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=247 PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=47093
Apr  4 21:33:51 UBN-1 kernel: [78846.131295] [IPTABLES ACCEPT] :IN=eth0 OUT= MAC=00:0c:29:e4:d1:06:10:60:4b:69:7e:fd:08:00 SRC=192.168.0.39 DST=192.168.0.47 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=250 PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=47094
Apr  4 21:33:52 UBN-1 kernel: [78847.132160] [IPTABLES ACCEPT] :IN=eth0 OUT= MAC=00:0c:29:e4:d1:06:10:60:4b:69:7e:fd:08:00 SRC=192.168.0.39 DST=192.168.0.47 LEN=60 TOS=0x00 PREC=0x00 TTL=128 ID=252 PROTO=ICMP TYPE=8 CODE=0 ID=2 SEQ=47095
......
......

Hope i have understood the question !

4
  • But matching NEW would give me the first handshake packet, i.e. SYN on TCP, correct? What I'm interested in is the first packet of the communications after the handshake (specifically, a TLS client hello in my case) -- sorry for being rather unclear about my goals.
    – N.A.
    Apr 4, 2014 at 19:56
  • @N.A Correct. Still unsure about what you want : iptables logging or packet capture ?
    – krisFR
    Apr 4, 2014 at 20:10
  • Logging, not capture as in tcpdump -- sorry about that. Specifically, as said, I'd like to log all TLS client hellos.
    – N.A.
    Apr 4, 2014 at 20:12
  • @N.A Ok. You cannot do this just using iptables logging. Follow @Evan Anderson advice to achieve this. As i was guessing, tcpdump is what you need and is your best friend in this case.
    – krisFR
    Apr 4, 2014 at 23:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.