Created
December 21, 2015 19:29
-
-
Save smihir/54c3f4f968f4383e065c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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
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.