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.

fragmented_packets

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="173.63.1.2"
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 173.63.1.2. 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       = 144.2.3.2
  dst       = 173.63.1.2
  \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       = 144.2.3.2
  dst       = 173.63.1.2
  \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 173.63.1.2
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)
    144.2.3.2.1500 > 173.63.1.2.1501: UDP, length 996
IP (tos 0x0, ttl 64, id 12345, offset 504, flags [none], proto UDP (17), length 520)
    144.2.3.2 > 173.63.1.2: ip-proto-17
IP (tos 0xc0, ttl 62, id 17715, offset 0, flags [none], proto ICMP (1), length 576)
    173.63.1.2 > 144.2.3.2: ICMP 173.63.1.2 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)!)
    144.2.3.2.1500 > 173.63.1.2.1501: UDP, length 996

3 packets captured
3 packets received by filter
0 packets dropped by kernel

Let me explain each line what happens here.

1st Packet

IP (tos 0x0, ttl 64, id 12345, offset 0, flags [+], proto UDP (17), length 524)
    144.2.3.2.1500 > 173.63.1.2.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.

2nd packet

IP (tos 0x0, ttl 64, id 12345, offset 504, flags [none], proto UDP (17), length 520)
    144.2.3.2 > 173.63.1.2: 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!

3rd packet

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 144.2.3.2.1500 > 173.63.1.2.1501: UDP, length 996
IP 144.2.3.2 > 173.63.1.2: 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: 144.2.3.2/1500 --> 173.63.1.2/1501;udp, If: ge-0/0/0.951, Pkts: 2, Bytes: 1044
  Out: 173.63.1.2/1501 --> 144.2.3.2/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!

About: rtoodtoo

Worked for more than 10 years as a Network/Support Engineer and also interested in Python, Linux, Security and SD-WAN // JNCIE-SEC #223 / RHCE / PCNSE


10 thoughts on “Fragmented IP packet forwarding”

  1. Somehow, you post new stuff just a 1 week beofre I actually need it – and it’s not the first time 🙂
    Love your posts. Make things clear in the junos land.

    Thanks for that!

    1. same question here, seems that doesn’t works for ICMP, that doens’t support fragment at all I just discover!!

  2. Thanks! Exactly what i’ve been looking for, good information about getting the packets fragmented.

  3. I used the following code to send out-of-order fragments to my destination.

    #!/usr/bin/python

    from scapy.all import *
    dip=”10.204.75.3″
    payload=”A”*496+”B”*500
    #packet=IP(dst=dip,id=12345)/UDP(sport=1500,dport=1501)/payload
    packet=IP(dst=dip,id=2402)/ICMP()/payload
    frags=fragment(packet,fragsize=300)

    counter=1
    for fragment in frags:
    print “Packet no#”+str(counter)
    print “===============================================”
    if counter == 2:
    counter+=1
    continue
    else:
    fragment.show()
    counter+=1
    send(fragment)
    send(frags[1])

    I am able to achieve what I want with this (send fragment 2 at the last). However, on the linux interface, the proto is not being detected as ICMP. On Wireshark, proto field is like below:

    Protocol: IPv6 Hop-by-Hop Option (0)

    What could be the issue?

    Note: If I don’t fragment and just say send(packet), all is fine.

    -Sandeep.

  4. Am fragmenting a PSH data based on MTU size using fragment() method, packet is successfully getting fragmented [ 3 fragments ] . But the sad part is only first fragmented packet is reaching server. Other two fragmented packet are not seeing at all in the wireshark . On client side logs are showing that send(fragment) is executed successfully for 3 times without failures. Can someone help on this.. Its really imp…

  5. Thanks a lot! This is really helped me with understanding how reassembly really works.
    Especially the note about that Wireshark default setting (which is really mess).

You have a feedback?

Discover more from RtoDto.net

Subscribe now to keep reading and get access to the full archive.

Continue reading