Skip to content

Instantly share code, notes, and snippets.

@smihir
Created December 21, 2015 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save smihir/54c3f4f968f4383e065c to your computer and use it in GitHub Desktop.
Save smihir/54c3f4f968f4383e065c 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()
@ChrisGottsacker
Copy link

Not cool. I'm taking CS 640 now and accidentally found this when I was looking up information on one of the Switchyard methods. You absolutely need to put this in a private repository or remove it from Github, otherwise you are enabling academic misconduct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment