About Fragmented IP packet forwarding.
I couldn’t really find a suitable topic for this post actually but I will try to find answers for the following questions:
- How can we fragment an IP packet manually in scapy
- How does a fragmented packet look like and how the transport layer (TCP/UDP) header is located
- How do we forward fragmented packets, do we reassemle them?
- If we don’t reassemble, can we force reassembly?
First of all a bit of a theory: if an incoming IP packet is to be forwarded to another next hop and the MTU of this new path is smaller than the packet to be transmitted, we must find a way to forward the packet. If the packet has DF (Don’t Fragment) bit on i.e we are instructed not to fragment the packet most probably by the source, then normally we are expected to send an ICMP packet with type “Fragmentation needed” and pray that on the way back to the source no devices block all ICMP type of traffic. Second scenario is that what if the source lets us fragment the packet. Then we need to fragment it and story from now on is about this part of the scenario and the topology we will use is something like below.
Scapy is a fantastic tool to generate your own packets. It is exremely flexible and in our example, we will perform the fragmentation of a packet via our script.
Once you install scapy, you create the following lovely script.
#!/usr/bin/python from scapy.all import * dip="188.8.131.52" payload="A"*496+"B"*500 packet=IP(dst=dip,id=12345)/UDP(sport=1500,dport=1501)/payload frags=fragment(packet,fragsize=500) counter=1 for fragment in frags: print "Packet no#"+str(counter) print "===================================================" fragment.show() #displays each fragment counter+=1 send(fragment)
What do we do here? In a nutshell, we create an IP packet transport protocol of which is UDP. Our payload contains only characters A and B:) I put 496 A and 500 B character so we have around ~1000bytes of payload and we instruct scapy to devide this total IP packet in 500 byte segments by fragmenting it and send it to IP address 184.108.40.206. Isn’t it so cool?
Now we will run the script and capture(tcpdump) on Debian1 device to see how the fragmented packets look like.
root@debian1:~/Python/Scapy# python frag.py WARNING: No route found for IPv6 destination :: (no default route?) Packet no#1 =================================================== ###[ IP ]### version = 4 ihl = None tos = 0x0 len = None id = 12345 flags = MF frag = 0 ttl = 64 proto = udp chksum = None src = 220.127.116.11 dst = 18.104.22.168 \options \ ###[ Raw ]### load = '\x05\xdc\x05\xdd\x03\xec\xbf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' . Sent 1 packets. Packet no#2 =================================================== ###[ IP ]### version = 4 ihl = None tos = 0x0 len = None id = 12345 flags = frag = 63 ttl = 64 proto = udp chksum = None src = 22.214.171.124 dst = 126.96.36.199 \options \ ###[ Raw ]### load = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' . Sent 1 packets.
We have run the script and 496bytes of A characters are transmitted on packet 1 and all 500bytes of B characters are transmitted on the second packet i.e we split the packet into two. Let’s see how the packet looks like on the wire.
root@debian1:~# tcpdump -tvvnni eth1.956 host 188.8.131.52 tcpdump: listening on eth1.956, link-type EN10MB (Ethernet), capture size 65535 bytes IP (tos 0x0, ttl 64, id 12345, offset 0, flags [+], proto UDP (17), length 524) 184.108.40.206.1500 > 220.127.116.11.1501: UDP, length 996 IP (tos 0x0, ttl 64, id 12345, offset 504, flags [none], proto UDP (17), length 520) 18.104.22.168 > 22.214.171.124: ip-proto-17 IP (tos 0xc0, ttl 62, id 17715, offset 0, flags [none], proto ICMP (1), length 576) 126.96.36.199 > 188.8.131.52: ICMP 184.108.40.206 udp port 1501 unreachable, length 556 IP (tos 0x0, ttl 62, id 12345, offset 0, flags [none], proto UDP (17), length 1024, bad cksum e962 (->76f)!) 220.127.116.11.1500 > 18.104.22.168.1501: UDP, length 996 3 packets captured 3 packets received by filter 0 packets dropped by kernelLet me explain each line what happens here.
IP (tos 0x0, ttl 64, id 12345, offset 0, flags [+], proto UDP (17), length 524) 22.214.171.124.1500 > 126.96.36.199.1501: UDP, length 996
We pushed 496A+500B bytes of payload of data to scapy. Dear scapy took 496bytes of this data which is all A characters and encapsulated with 8 bytes of UDP header + 20 bytes of IP header which is in total = 524 bytes. Pay attention to the port numbers. Those are the UDP port numbers we set in the code. UDP length shows 996bytes since our payload is this number of bytes in total. ID number is 12345 and it is the same on 1st and 2nd packet. Offset is also 0 as this is the first packet. Although we can’t see on this output, we have also More Fragment bit is on.
IP (tos 0x0, ttl 64, id 12345, offset 504, flags [none], proto UDP (17), length 520) 188.8.131.52 > 184.108.40.206: ip-proto-17
Real fun begins here. Where are the port numbers? We don’t have them on the second packet as the UDP header is on the first packet. You can see this from the packet size. 500bytes(B) payload + 20 bytes IP header i.e no room for header. The evidence of fragmentation is the offset but why is it 504? This field specifies how far we are from the beginning of the unfragmented IP packet and I believe it counts UDP header too:) so our offset should be 496A + 8bytes UDP header = 504.
Note: If you display the same packets in Wireshark, due to the default setting “Reassemble fragmented IPv4 datagrams“, it misleads you to think that UDP header is on the second packet instead of the first one. Be careful!
This isn’t even a UDP segment. Because we are sending UDP traffic to a port on which there isn’t any listening socket, remote side sends back an ICMP (Destination port unreachable) notification message.
So far we have found the answers to first two questions. Now we need to see how our stateful firewall forwards these packets. I am running tcpdump on hostE destination device this time.
root@hostE:~# tcpdump -tnni eth1.963 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1.963, link-type EN10MB (Ethernet), capture size 65535 bytes IP 220.127.116.11.1500 > 18.104.22.168.1501: UDP, length 996 IP 22.214.171.124 > 126.96.36.199: ip-proto-17 ^C 2 packets captured 2 packets received by filter 0 packets dropped by kernel
We can see that we have received 2 fragmented packets first of which has the UDP header.
Note: You don’t see the ICMP destination reachable message as I blocked it via iptables otherwise, it clears SRX session immediately and we can’t display the session.
Session ID: 132, Policy name: allow-from-Internet/4, Timeout: 58, Valid In: 188.8.131.52/1500 --> 184.108.40.206/1501;udp, If: ge-0/0/0.951, Pkts: 2, Bytes: 1044 Out: 220.127.116.11/1501 --> 18.104.22.168/1500;udp, If: ge-0/0/0.963, Pkts: 0, Bytes: 0 Total sessions: 1
and the one above is the flow session entry. 2 packets have been forwarded in total 1044 bytes. We don’t have return packet as it is one way traffic.
IP fragmentation reassembly normally is performed at the destination host unless there is a device in the path which needs this reassembly (e.g IDP) but there is a command which does this for us. This is just to demonstrate the option. You don’t really need to use it
set security flow force-ip-reassembly commit
You can see reassembly being done if you enable flow traceoptions as the firewall forwards fragmented packets as is to the destination host.
I have tried to give a bit of information about fragmentation and its relation to transport layer protocol UDP. I hope this was helpful!