Skip to content

Instantly share code, notes, and snippets.

@aishusreeni
Forked from smihir/router.py
Created March 14, 2018 20:14
Show Gist options
  • Select an option

  • Save aishusreeni/4c62734a020255d7e46d1d892323e799 to your computer and use it in GitHub Desktop.

Select an option

Save aishusreeni/4c62734a020255d7e46d1d892323e799 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
'''
Basic IPv4 router (static routing) in Python.
'''
import sys
import os
import time
from switchyard.lib.packet import *
from switchyard.lib.address import *
from switchyard.lib.common import *
class Router(object):
_forwarding_table_file = "forwarding_table.txt"
def __init__(self, net):
self.net = net
self.last_to = 0
# list of dictionaries
self.forwarding_table = list()
#dict with ip address as key
self.arp_table = dict()
self.arp_waitlist = dict()
self.queue = list()
self.create_forwarding_table()
def create_forwarding_table(self):
with open(self._forwarding_table_file) as f:
for line in f:
linearray = line.strip('\n').split(' ')
if len(linearray) != 4:
log_fatal("incorrect format")
continue
fwtable_entry = dict()
fwtable_entry['netaddr'] = linearray[0]
fwtable_entry['netmask'] = linearray[1]
fwtable_entry['nexthop'] = linearray[2]
fwtable_entry['intfname'] = linearray[3]
fwtable_entry['prefixlen'] = IPv4Network(fwtable_entry['netaddr'] + '/' + fwtable_entry['netmask']).prefixlen
self.forwarding_table.append(fwtable_entry)
for i in self.net.interfaces():
fwtable_entry = dict()
# should be ip & netmask
fwtable_entry['netaddr'] = str(IPv4Address(int(IPv4Address(i.ipaddr)) & int(IPv4Address(i.netmask))))
fwtable_entry['netmask'] = str(i.netmask)
#fwtable_entry['nexthop'] = str(i.ipaddr) # fix this as well, should be NULL
fwtable_entry['intfname'] = i.name
fwtable_entry['prefixlen'] = IPv4Network(fwtable_entry['netaddr'] + '/' + fwtable_entry['netmask']).prefixlen
self.forwarding_table.append(fwtable_entry)
self.forwarding_table.sort(key = lambda x:x['prefixlen'], reverse = True)
log_debug(self.forwarding_table)
def get_tx_dev(self, dstip):
dstaddr = IPv4Address(dstip)
for e in self.forwarding_table:
prefixnet = IPv4Network(str(e['netaddr']) + '/' + str(e['prefixlen']))
#print("prefixnet {}, dstip {}".format(prefixnet, dstip))
if dstaddr in prefixnet:
return e['intfname']
return None
def get_next_hop(self, dstip):
dstaddr = IPv4Address(dstip)
for e in self.forwarding_table:
prefixnet = IPv4Network(str(e['netaddr']) + '/' + str(e['prefixlen']))
#print("prefixnet {}, dstip {}".format(prefixnet, dstip))
if dstaddr in prefixnet:
if 'nexthop' in e:
return e['nexthop']
else:
return dstip
return None
def get_dst_mac(self, ip):
if ip in self.arp_table:
return self.arp_table[ip]
return None
def add_arp_entry(self, ip, mac):
self.arp_table[ip] = mac
def process_queue_for_mac(self, ip, mac):
log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
log_debug("queue: {}".format(str(self.queue)))
delqueue = list()
for e in self.queue:
if e['ip'] == ip:
#send
tx_pkt = self.transform_ipv4_pkt(e['outdev'], e['pkt'], mac)
log_debug("Send packet({}): {}".format(e['outdev'], str(tx_pkt)))
self.net.send_packet(e['outdev'], tx_pkt)
#delete.from queue
#self.queue.remove(e)
delqueue.append(e)
log_debug("queue: {}".format(str(self.queue)))
for i in delqueue:
self.queue.remove(i)
#delete from the set
print(self.arp_waitlist)
self.arp_waitlist.pop(ip, 0)
def create_arp_waitlist(self, ip):
if not ip in self.arp_waitlist:
self.arp_waitlist[ip] = 0
def queue_pkt(self, pkt, out_dev, next_hop):
node = dict()
node['ip'] = next_hop
node['pkt'] = pkt
node['outdev'] = out_dev
self.queue.append(node)
def transform_ipv4_pkt(self, tx_dev, pkt, dst_mac):
pkt[0].src = self.net.interface_by_name(tx_dev).ethaddr
pkt[0].dst = dst_mac
return pkt
def process_pending_packets(self):
t = time.time()
if t < self.last_to + 1:
return
self.last_to = t
log_debug("{} process_pending_packets".format(str(t)))
log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
log_debug("queue: {}".format(str(self.queue)))
poplist = list()
for key, val in self.arp_waitlist.items():
self.arp_waitlist[key] += 1
if self.arp_waitlist[key] >= 5:
poplist.append(key)
delqueue = list()
for e in self.queue:
if key == e['ip']:
log_debug("{} removing entry from queue {}".format(str(t), str(e)))
#self.queue.remove(e)
delqueue.append(e)
for i in delqueue:
self.queue.remove(i)
else:
devname = None
# send arp request
for e in self.queue:
if key == e['ip']:
devname = e['outdev']
break
if devname != None:
tx_dev = self.net.interface_by_name(devname)
tx_pkt = create_ip_arp_request(tx_dev.ethaddr, tx_dev.ipaddr, e['ip'])
log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
self.net.send_packet(devname, tx_pkt)
else:
log_debug("process_pending_packets: outdev is None!")
for ip in poplist:
self.arp_waitlist.pop(ip, 0)
log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
log_debug("queue: {}".format(str(self.queue)))
def router_main(self):
'''
Main method for router; we stay in a loop in this method, receiving
packets until the end of time.
'''
while True:
gotpkt = True
try:
dev,pkt = self.net.recv_packet(timeout=1.0)
except NoPackets:
self.process_pending_packets()
log_debug("No packets available in recv_packet")
gotpkt = False
#if no ARP response for 1 sec, retry ARP for 5 times then timeout(i.e drop current packet)
except Shutdown:
log_debug("Got shutdown signal")
break
if gotpkt:
log_debug("Got a packet: {}".format(str(pkt)))
arp = pkt.get_header(Arp)
if arp and arp.operation == ArpOperation.Request:
# check if it is for us
try:
intf = self.net.interface_by_ipaddr(arp.targetprotoaddr)
#if we are here it means ARP resquest is for us
tx_pkt = create_ip_arp_reply(intf.ethaddr, arp.senderhwaddr, arp.targetprotoaddr, arp.senderprotoaddr)
log_debug("Send packet({}): {}".format(dev, str(tx_pkt)))
self.net.send_packet(dev, tx_pkt)
except SwitchyException:
log_debug("Rx Arp not for us packet({}): {}".format(dev, str(tx_pkt)))
finally:
self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
self.process_pending_packets()
continue
if arp and arp.operation == ArpOperation.Reply:
try:
intf = self.net.interface_by_ipaddr(arp.targetprotoaddr)
#if we are here it means ARP response is for us
'''
self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
self.process_queue_for_mac(str(arp.senderprotoaddr), str(arp.senderhwaddr))
self.process_pending_packets()
continue
'''
except SwitchyException:
log_debug("Arp reply not for us, ip({})".format(arp.targetprotoaddr))
finally:
self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
self.process_queue_for_mac(str(arp.senderprotoaddr), str(arp.senderhwaddr))
self.process_pending_packets()
continue
self.process_pending_packets()
ipv4 = pkt.get_header(IPv4)
if ipv4:
pkt[1].ttl -= 1
log_debug("ipv4 dst {} ip({})".format(dev, ipv4.dst))
try:
#if for self drop
intf = self.net.interface_by_ipaddr(ipv4.dst)
if intf != None:
log_debug("packet for self, drop ({}): {}".format(dev, str(pkt)))
continue
except SwitchyException:
#check in forwarding table for match with longest prefix
tx_dev = self.get_tx_dev(ipv4.dst)
if (tx_dev == None):
#if not found drop
log_debug("no tx_dev found, drop ({})".format(str(pkt)))
continue
log_debug("tx_dev {} found, process packet({})".format(tx_dev, str(pkt)))
next_hop = self.get_next_hop(ipv4.dst)
if (next_hop == None):
next_hop = ipv4.dst
#if found, check if the arp entry for next hop is present in ARP table
dst_mac = self.get_dst_mac(str(next_hop))
log_debug("Next Hop {} Dst mac".format(str(next_hop), str(dst_mac)))
#if entry reconstruct ethernet header and send
if (dst_mac != None):
log_debug("Entry found in ARP table {} process packet({})".format(dst_mac, str(pkt)))
tx_pkt = self.transform_ipv4_pkt(tx_dev, pkt, dst_mac)
log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
self.net.send_packet(tx_dev, tx_pkt)
#if no entry, then check in per interface queue if ARP request is sent
else:
log_debug("Entry not found in ARP table, process packet({})".format(str(pkt)))
log_debug("Arp table {}".format(str(self.arp_table)))
if not str(ipv4.dst) in self.arp_waitlist:
log_debug("Entry not found in ARP waitlist, create entry and add to q({})".format(str(self.queue)))
self.create_arp_waitlist(str(next_hop))
self.queue_pkt(pkt, tx_dev, str(next_hop))
log_debug("q({})".format(str(self.queue)))
src_mac = self.net.interface_by_name(tx_dev).ethaddr
src_ip = self.net.interface_by_name(tx_dev).ipaddr
tx_pkt = create_ip_arp_request(src_mac, src_ip, next_hop)
log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
self.net.send_packet(tx_dev, tx_pkt, str(next_hop))
else:
log_debug("Entry found in ARP waitlist, create entry and add to q({})".format(str(self.queue)))
self.queue_pkt(pkt, tx_dev, str(next_hop))
log_debug("q({})".format(str(self.queue)))
#if sent put in queue
#if not sent create a new queue and send arp
#if arp response reconstruct ethernet header and send
continue
def switchy_main(net):
'''
Main entry point for router. Just create Router
object and get it going.
'''
r = Router(net)
r.router_main()
net.shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment