A customer asked me at some point if we could evaluate how the CoPP DDOS automated filters on a Juniper MX are triggered and how fast they respond to different types of packets. As such I needed to craft custom traffic. I was not very good at coming up with it for Ostinato or Spirent so I used Scapy to craft the packet. Afterward I took the packet hexdump and input it into Spirent / Ostinato as what the streams from there should generate (that hexdump has indeed all data needed, source/dst addresses included which meant of course that it needs to match what Spirent thinks it has on the interface facing the Juniper device).

Examples of generated packets:

  • OSPFv3 IPSEC Encrypted Packets
  • BGP Open Packet
  • BGP IPv6 Open Packet
  • IPSEC ESP Packet
  • BGP Update Packet
  • ICMP Echo Request
  • BFD echo

Table of Contents

Scapy did not really work as initially expected. I needed a specific version of one of its dependencies or else it would fail upon installing. /usr/local/Cellar/python/2.7.12/bin/pip2.7 install pyx==0.12.1

My examples also show 1-2-3 ways of doing the same thing (you will see that I did not respect the same pattern/way of generating packets all the time).

OSPFv3 IPSEC encrypted packet

$ sudo scapy
>>> load_contrib("ospf")
>>> sa = SecurityAssociation(ESP, spi=0x00000100, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
>>> p6 = IPv6(src='fe80::2', dst='FF02::5')
>>> p6 = p6/OSPFv3_Hdr(src='11.11.11.2',area='0.0.0.0')
>>> p6 = p6/OSPFv3_Hello(router='11.11.11.2',neighbors='11.11.11.1')
>>> p6 = p6/OSPFv3_Hello(router='11.11.11.2',neighbors='11.11.11.1')
>>> p6 /= Raw('testdata')
>>> p6 = IPv6(str(p6))
>>> e6 = sa.encrypt(p6)
>>> send(e6,count=1)	

or copying the hexdump of a packet from Wireshark, then insert it into a Spirent or Ostinato stream (not defining any other stream properties as this hexdump has all the data). Alternatively, you might need to change the SRC Mac / DST Mac address in the hex output (quite easy to spot the hex representation of the actual mac address which is known by the user).:

BGP Open Packet


m scapy.all import *
>>> pkt = Ether(dst="00:10:94:00:00:05",src="00:26:88:5f:7a:95")/IP(src="11.11.11.2",dst="11.11.11.1")/TCP(sport=1025,dport=179)/BGPOpen(AS=65199,hold_time=90,bgp_id="11.11.11.2")

>>> pkt.show()
###[ Ethernet ]###
  dst       = 00:10:94:00:00:05
  src       = 00:26:88:5f:7a:95
  type      = 0x800
###[ IP ]###
     version   = 4
     ihl       = None
     tos       = 0x0
     len       = None
     id        = 1
     flags     =
     frag      = 0
     ttl       = 64
     proto     = tcp
     chksum    = None
     src       = 11.11.11.2
     dst       = 11.11.11.1
     \options   \
###[ TCP ]###
        sport     = blackjack
        dport     = bgp
        seq       = 0
        ack       = 0
        dataofs   = None
        reserved  = 0
        flags     = S
        window    = 8192
        chksum    = None
        urgptr    = 0
        options   = {}
###[ BGP Open Header ]###
           version   = 4
           AS        = 65199
           hold_time = 90
           bgp_id    = 11.11.11.2
           opt_parm_len= None
           \opt_parm  \

>>> wrpcap('/tmp/bgpopen.pcap',pkt)

and then with wireshark

OR

>>> linehexdump(pkt)
00 10 94 00 00 05 00 26 88 5F 7A 95 08 00 45 00 00 32 00 01 00 00 40 06 4E AD 0B 0B 0B 02 0B 0B 0B 01 04 01 00 B3 00 00 00 00 00 00 00 00 50 02 20 00 43 F7 00 00 04 FE AF 00 5A 0B 0B 0B 02 00  .......&._z...E..2....@.N.....................P. .C.......Z.....
(eliminate spaces, take just the hex, copy paste in spirent -> create new frame -> custom)

BGP IPv6 Open Packet

>>> pkt = Ether(dst="00:10:94:00:00:05",src="00:26:88:5f:7a:95")/IPv6(src="2001:db8::2",dst="2001:db8::1")/TCP(sport=1025,dport=179)/BGPOpen(AS=65199,hold_time=90,bgp_id="11.11.11.2")

>>> linehexdump(pkt)
00 10 94 00 00 05 00 26 88 5F 7A 95 86 DD 60 00 00 00 00 1E 06 40 20 01 0D B8 00 00 00 00 00 00 00 00 00 00 00 02 20 01 0D B8 00 00 00 00 00 00 00 00 00 00 00 01 04 01 00 B3 00 00 00 00 00 00 00 00 50 02 20 00 14 9B 00 00 04 FE AF 00 5A 0B 0B 0B 02 00  .......&._z...`......@ ............... ...........................P. .........Z.....

put in a file, /tmp/t

# cat /tmp/p  | tr -d " "
0010940000050026885F7A9586DD60000000001E064020010DB800000000000000000000000220010DB8000000000000000000000001040100B3000000000000000050022000149B000004FEAF005A0B0B0B0200

IPSEC ESP Packet

a = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
>>> print sa
<scapy.layers.ipsec.SecurityAssociation object at 0x10638d510>
>>> p = IP(src='1.1.1.1', dst='2.2.2.2')
>>> p /= TCP(sport=45012, dport=80)
>>> p /= Raw('testdata')
>>> print p


>>> p = IP(str(p))

>>> e = sa.encrypt(p)
>>> send(e)

>>> send(e,count=20000)

This last example also sends (like a flood) such packets out. In real-life though a laptop or normal compute would not scale at generating traffic/flooding for security purpose testing a network router with enough capacity.

IPSEC ESP Packet

load_contrib("ospf")
sa = SecurityAssociation(ESP, spi=0x00000100, crypt_algo='AES-CBC',crypt_key='sixteenbytes key')
p6 = IPv6(src='fe80::3ac9:86ff:fe43:a72a', dst='FF02::5')

p6 = p6/OSPFv3_Hdr(src='127.0.0.15',area='0.0.0.0')
p6 = p6/OSPFv3_Hello(router='127.0.0.15',neighbors='127.0.0.10')
	
p6 /= Raw('testdata')
p6 = IPv6(str(p6))
e6 = sa.encrypt(p6)
send(e6,count=20000)

BGP Update Packet

a) /usr/local/lib/python2.7/site-packages/scapy/config.py

add bgp here
   load_layers = ["l2", "inet", "dhcp", "dns", "dot11", "gprs", "tls",
                   "hsrp", "inet6", "ir", "isakmp", "l2tp", "mgcp",
                   "mobileip", "netbios", "netflow", "ntp", "ppp",
                   "radius", "rip", "rtp", "skinny", "smb", "snmp",
                   "tftp", "x509", "bluetooth", "dhcp6", "llmnr",
                   "sctp", "vrrp", "ipsec", "lltd", "vxlan", "bgp"]

b) copy it from contrib into the layers folder
mtanasescu-mbp:2.7 mtanasescu$ cp .//lib/python/site-packages/scapy/contrib/bgp.py .//lib/python/site-packages/scapy/layers/
/Users/mtanasescu/Library/Python/2.7/lib/python/site-packages/scapy/contrib/bgp.py
c) >>> from scapy.all import send, IP, ICMP, UDP, Raw, TCP

pkt = IP()/TCP()/BGPOpen()

>>> p = IP(src='10.10.10.2', dst='10.10.10.1')
>>> p /= TCP(sport=49152,dport=179)

// how to assign values - if multiple layers then IP()/Ether( ... value)..
// e=Ether(dst='02:35:b9:f4:00:01')
// i1=IP(dst='10.0.0.0',flags='MF')
// i2=IP(dst='10.0.0.0',frag=60,proto=17)
// OR

>>> pkt.dport="bgp"


WORKING PART
>>> pkt.src="10.10.10.2"
>>> pkt.dst="10.10.10.1"
>>> pkt.sport=900
>>> pkt.dport=179
>>> pkt.AS=65199
>>> pkt.hold_time=90
>>> pkt.bgp_id="10.10.10.2"
>>> pkt.show()
###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 64
  proto= tcp
  chksum= None
  src= 10.10.10.2
  dst= 10.10.10.1
  \options\
###[ TCP ]###
     sport= omginitialrefs
     dport= bgp
     seq= 0
     ack= 0
     dataofs= None
     reserved= 0
     flags= S
     window= 8192
     chksum= None
     urgptr= 0
     options= {}
###[ BGP Open Header ]###
        version= 4
        AS= 65199
        hold_time= 90
        bgp_id= 10.10.10.2
        opt_parm_len= None
        \opt_parm\

#LAYER-2 ..but if it is not resolved or something..it goes to nirvana 
>>> sendp(pkt,iface="en3",loop=True,inter=0.001)

#LAYER-3 uses already layer-2 on the interface configured ..=> we need proper IPs and config from the OS, this is why interface cannot be specified
>>> send(pkt,loop=True,inter=0.001)

ICMP Echo Request

cmp = ICMP()
# set the destinaion IP to the target
ip.dst = ip_target
# create 2 casade loops for sending the icmp packet
for i_type in range(0,256):
  for i_code in range(0,256):
    icmp.type = i_type
    icmp.code = i_code
    print i_type,i_code
    send(ip/icmp)

type8, code 0 for echo request

OR

#! /usr/bin/env python

from scapy.all import send, IP, ICMP
>>> send(IP(src="10.10.10.2",dst="10.10.10.1")/ICMP()/"Hello World",loop=True,inter=0.00000001)

SEND vs SENDP

In scapy, the send() function will send packets at layer 3. That is to say it will handle routing and layer 2 for you.  >» send(IP(dst=“1.2.3.4”)/ICMP())

The sendp() function will work at layer 2. It’s up to you to choose the right interface and the right link layer protocol.  >» sendp(Ether()/IP(dst=“1.2.3.4”,ttl=(1,4)), iface=“eth1”)

BFD echo

(or sort of)

>>> from scapy.all import send, IP, ICMP, UDP, Raw
>>> p = IP(src='1.1.1.1', dst='2.2.2.2')
>>> p /= UDP(sport=49152, dport=3785)
>>> p /= Raw('000000000a0a0a0100000239')
>>> print p
>>> p = IP(str(p))
>>> send(p,count=20000)

Read PCAP with Scapy

>» a=rdpcap("/spare/captures/isakmp.cap")

Display packet made with Scapy

#!/usr/bin/python
from scapy.all import *

e=Ether(dst='02:35:b9:f4:00:01')
i1=IP(dst='10.0.0.0',flags='MF')
i2=IP(dst='10.0.0.0',frag=60,proto=17)
i3=IP(dst='10.0.0.0')
u=UDP(sport=1024,dport=1024)

p1=e/i1/u/pl
p1.show()