def __init__(self, flow_id, source, destination, amount, env, time, bw): """ Constructor for Flow class """ self._flow_id = flow_id self._src = source self._dest = destination self._amount = amount * 8 * 10**6 self._pack_num = 0 self._cwnd = 1.0 self._ssthresh = 1000 self._resend_time = 10 self._min_RTT = 1000.0 self._last_RTT = 3000.0 self._SRTT = -1 self._RTTVAR = 0 self._RTO = 3000 self._packets_sent = [] self._packets_time_out = [] self._acks_arrived = set() self.env = env self.bw = bw self._flow_start = time * 1000.0 self._last_packet = 0 self._done = 0 self._send_rate = Rate_Graph(self._flow_id, "flow {0} send rate".format(self.flow_id), self.env, self.bw) self._receive_rate = Rate_Graph( self._flow_id, "flow {0} receive" " rate".format(self.flow_id), self.env, self.bw) self.env.add_event( Event("Start flow", self._flow_id, self.send_packet), self._flow_start)
def __init__(self, id, device_a, device_b, delay, rate, capacity, env, bw): self._id = id self._device_a = device_a self._device_b = device_b # rate is initially Mbps. rate is stored as bits per ms. self._rate = rate * 10 ** 3 self._delay = delay # capacity is initially KB. capacity is stored as bits. self._capacity = capacity * 1000 * 8 # Buffer to enter link self._release_into_link_buffer = deque() # Environment variables self.env = env self.bw = bw # Buffer size. Initialize to 0 since there are no packets. self._size = 0 self._distance = delay # Recorder for rate data self._send_rate = Rate_Graph(self._id, "link {0} send rate".format(self._id), self.env, self.bw)
def __init__(self, router_id, env, bw): """Constructor for Router class.""" super(Router, self).__init__(router_id) self.env = env self.bw = bw self._routing_table = {} self._new_routing_table = {} self._send_rate = Rate_Graph(router_id, "router {0} send rate".format(router_id), self.env, self.bw) self._receive_rate = Rate_Graph( router_id, "router {0} receive" " rate".format(router_id), self.env, self.bw) self.env.add_event( Event("{} sent routing" " packet".format(self._network_id), self._network_id, self.start_new_routing), 0)
def __init__(self, router_id, env, bw): """Constructor for Router class.""" super(Router, self).__init__(router_id) self.env = env self.bw = bw self._routing_table = {} self._new_routing_table = {} self._send_rate = Rate_Graph(router_id, "router {0} send rate".format(router_id), self.env, self.bw) self._receive_rate = Rate_Graph(router_id, "router {0} receive" " rate".format(router_id), self.env, self.bw) self.env.add_event(Event("{} sent routing" " packet".format(self._network_id), self._network_id, self.start_new_routing), 0)
def __init__(self, flow_id, source, destination, amount, env, time, bw): """ Constructor for Flow class """ self._flow_id = flow_id self._src = source self._dest = destination self._amount = amount*8*10**6 self._pack_num = 0 self._cwnd = 1.0 self._ssthresh = 1000 self._resend_time = 10 self._min_RTT = 1000.0 self._last_RTT = 3000.0 self._SRTT = -1 self._RTTVAR = 0 self._RTO = 3000 self._packets_sent = [] self._packets_time_out = [] self._acks_arrived = set() self.env = env self.bw = bw self._flow_start = time*1000.0 self._last_packet = 0 self._done = 0 self._send_rate = Rate_Graph(self._flow_id, "flow {0} send rate".format(self.flow_id), self.env, self.bw) self._receive_rate = Rate_Graph(self._flow_id, "flow {0} receive" " rate".format(self.flow_id), self.env, self.bw) self.env.add_event(Event("Start flow", self._flow_id, self.send_packet), self._flow_start)
class Router(Device): """Class for routers. Routers are responsible for initializing and updating their routing table, and sending packets based on their routing table. Parameters ---------- router_id : string A unique id for the router. Attributes ---------- network_id : string A unique id of the device in the network. links : list A list of links that the router is connected to. routing_table : dict A dictionary representing the router's routing table. new_routing_table : dict A dictionary representing the router's new routing table. env : `Network` The network that the link belongs to. bw : `Blackwidow` BlackWidow simulation object containing simulation settings. send_rate : Rate_Graph object Send rate graphing object. receive_rate : Rate_Graph object Receive rate graphing object. Methods ------- add_link(link) Adds a link to the router. send(packet) Sends a packet to a link. receive(packet) Receives a packet from a link. start_new_routing() Starts a new routing round. send_routing() Sends a routing packet to all neighbors. update_route() Update the new_routing_table based on routing packets. _distance(link) Gets the distance of a link. """ def __init__(self, router_id, env, bw): """Constructor for Router class.""" super(Router, self).__init__(router_id) self.env = env self.bw = bw self._routing_table = {} self._new_routing_table = {} self._send_rate = Rate_Graph(router_id, "router {0} send rate".format(router_id), self.env, self.bw) self._receive_rate = Rate_Graph(router_id, "router {0} receive" " rate".format(router_id), self.env, self.bw) self.env.add_event(Event("{} sent routing" " packet".format(self._network_id), self._network_id, self.start_new_routing), 0) def add_link(self, link): """Overrides Device.add_link() to add to routing table. Parameters ---------- link : Link The link to add to the router. """ self._links.append(link) network_id = link._device_a.network_id if (network_id == self._network_id): network_id = link._device_b.network_id self._routing_table[network_id] = {'link': link, 'distance': self._distance(link)} self._new_routing_table[network_id] = \ {'link': link, 'distance': self._distance(link)} def send(self, packet): """Send packet to appropriate link. First looks in the new routing table to see if we know how to reach it there. Otherwise uses the old routing table. Parameters ---------- packet : Packet Packet to send through the router. """ route = None self._send_rate.add_point(packet, self.env.time) if packet.dest.network_id in self._new_routing_table: route = self._new_routing_table[packet.dest.network_id] elif packet.dest.network_id in self._routing_table: route = self._routing_table[packet.dest.network_id] if route is not None and 'link' in route: route['link'].receive(packet, self._network_id) def receive(self, packet): """Process packet by sending it out. If the packet is routing, calls update_route to update the new_routing_table. Parameters ---------- packet : Packet Received packet. """ self._receive_rate.add_point(packet, self.env.time) if packet.is_routing: self.update_route(packet) print "{} received routing packet from {}".format(self._network_id, packet.src) else: self.send(packet) def start_new_routing(self): """Start a new routing round. If there is dynamic routing, updates the routing table to the new routing table built up by dynamic routing and measures the distance for each link. """ # Reset routing table if dynamic routing. if not self.bw.static_routing: self._new_routing_table = {} for link in self._links: link.measure_distance() network_id = link._device_a.network_id if (network_id == self._network_id): network_id = link._device_b.network_id self._new_routing_table[network_id] = \ {'link': link, 'distance': self._distance(link)} self._routing_table = self._new_routing_table if self.env.time < 500: self.env.add_event(Event("{} reset its routing" " table.".format(self._network_id), self._network_id, self.start_new_routing), 10) else: self.env.add_event(Event("{} reset its routing" " table.".format(self._network_id), self._network_id, self.start_new_routing), 5000) self.send_routing() def send_routing(self): """Send routing packets to all neighbors.""" for link in self._links: other_device = link._device_a if (other_device.network_id == self._network_id): other_device = link.device_b if type(other_device) is Router: packet = RoutingPacket(ROUTING_PKT_ID, self._network_id, other_device.network_id, None, self._new_routing_table, self.bw.routing_packet_size) link.receive(packet, self._network_id) print "Sent routing packet from {}".format(self._network_id) def update_route(self, packet): """Update routing table. Goes through the routing table contained in the routing packet and determines if it contains a better way to get to each destination. This uses a distributed version of the Bellman-Ford algorithm. Parameters ---------- packet : Packet Routing packet to update the route. """ link = None if packet.src in self._new_routing_table: route = self._new_routing_table[packet.src] if 'link' in route: link = route['link'] else: raise ValueError('{} not found in {} \'s routing table.'.format( packet.src, self._network_id)) route_changed = False for dest, route in packet.routing_table.items(): distance = route['distance'] + link.distance if dest not in self._new_routing_table: self._new_routing_table[dest] = {'link': link, 'distance': distance} route_changed = True elif distance < self._new_routing_table[dest]['distance']: self._new_routing_table[dest] = {'link': link, 'distance': distance} route_changed = True if route_changed: self.send_routing() def _distance(self, link): """Get the distance of the link. Parameters ---------- link : Link Link to get distance of. """ distance = link.delay + link.get_buffer_size() / float(link.rate) if self.bw.static_routing: distance = link.delay return distance
class Link(object): """Simulates a link connected to two Devices in the network. Represents a physical link in the network. In addition to simple send and receive, this class also handles the packet buffer for sending packets. Parameters ---------- id : string A unique id for the link. device_a : `Device` A `Device` to which the link is connected. device_b : `Device` A `Device` to which the link is connected. delay : float The propagation delay to send packets across the link. Specified in ms. rate : float The rate at which the link can send a packet. Specified in Mbps. capacity : int The capacity of the link buffer. Specified in KB. env : `Network` The network that the link belongs to. bw : `Blackwidow` The simulation object containing settings and data recording. Attributes ---------- id : string The link id. device_a : `Device` One of the `Device` objects to which the link is connected. device_b : `Device` One of the `Device` objects to which the link is connected. delay : float The progapation delay in ms. rate : float The rate at which the link can send a packet in bits per ms. capacity : int The capacity of the link buffer in bits. distance : float The distance of the link. Used for dynamic routing. Methods ------- receive(packet, source_id) Receives a packet from a `Device`. measure_distance() Measures the link distance. """ def __init__(self, id, device_a, device_b, delay, rate, capacity, env, bw): self._id = id self._device_a = device_a self._device_b = device_b # rate is initially Mbps. rate is stored as bits per ms. self._rate = rate * 10 ** 3 self._delay = delay # capacity is initially KB. capacity is stored as bits. self._capacity = capacity * 1000 * 8 # Buffer to enter link self._release_into_link_buffer = deque() # Environment variables self.env = env self.bw = bw # Buffer size. Initialize to 0 since there are no packets. self._size = 0 self._distance = delay # Recorder for rate data self._send_rate = Rate_Graph(self._id, "link {0} send rate".format(self._id), self.env, self.bw) def __str__(self): """Returns a string representation of the link.""" msg = "Link {0} connected to {1} and {2}\n" msg += "\t Rate: {3} mbps\n" msg += "\t Delay: {4} mbps\n" msg += "\t Capacity: {5} bits\n" return msg.format(self._id, self._device_a.network_id, self._device_b.network_id, self._rate, self._delay, self._capacity) # Properties for attributes # Link id @property def id(self): return self._id @id.setter def id(self, value): raise AttributeError("Cannot modify link id: {0}".format(self._id)) # Link devices @property def device_a(self): return self._device_a @device_a.setter def device_a(self, value): raise AttributeError("Cannot modify link device: {0}".format(self._id)) @property def device_b(self): return self._device_b @device_b.setter def device_b(self, value): raise AttributeError("Cannot modify link device: {0}".format(self._id)) # Propagation delay @property def delay(self): return self._delay @delay.setter def delay(self, value): raise AttributeError("Cannot modify link delay: {0}".format(self._id)) # Link rate @property def rate(self): return self._rate @rate.setter def rate(self, value): raise AttributeError("Cannot modify link rate: {0}".format(self._id)) # Link capacity @property def capacity(self): return self._capacity @capacity.setter def capacity(self, value): raise AttributeError("Cannot modify link" " capacity: {0}".format(self._id)) # Distance of link @property def distance(self): return self._distance @distance.setter def distance(self, value): raise AttributeError("Cannot modify link" " distance: {0}".format(self._id)) def receive(self, packet, source_id): """Receives a packet from a `Device`. This function takes as parameter a `Packet` and a device id. Packets are either enqueued in the link buffer if the link buffer is not full or are dropped. Parameters ---------- packet : `Packet` The packet received by the link. source_id : string The id of the `Device` object sending the packet. """ # Add packet to link buffer as soon as it is received. # Drop packet if the buffer is full message = "I am link {0}. I have received " if packet.is_ack: message += "ACK " message += "packet {1} at time {2}" print message.format(self._id, packet.pack_id, self.env.time) # The buffer is not yet full, so enqueue the packet if self._size + packet.size < self._capacity: self._release_into_link_buffer.appendleft( [packet, source_id, self.env.time]) self._size += packet.size self.bw.record('{0}, {1}'.format(self.env.time, self._size), 'link_{0}.buffer'.format(self._id)) print "Current size of link {}: {}".format(self._id, self._size) # If we only have one packet in the buffer, send it with no delay if len(self._release_into_link_buffer) == 1: # Begin sending the packet in the link self._send() # The buffer is full else: print "Packet dropped." self.bw.record('{0}'.format(self.env.time), 'link_{0}.drop'.format(self._id)) def _send(self): """Sends the first packet in the buffer across the link. Notes ----- The packet begins to transmit across the link after size / rate time, where size is the packet size and rate is the rate of the link. This function then calls _release to send the packet to the receiving `Device`. """ # Get the first packet in the buffer. We do not dequeue until it has # fully been sent. packet_info = self._release_into_link_buffer[-1] # packet_info is a tuple of packet, source_id packet = packet_info[0] source_id = packet_info[1] # Calculate the delay time needed to begin sending the packet. delay = float(packet.size) / float(self._rate) # Create the event message msg = "I am link {0}. I have begun sending " if packet.is_ack: msg += "ACK " msg += "packet {1}" # Call _release after delay time to begin sending the packet. self.env.add_event(Event(msg.format(self._id, packet.pack_id), self._id, self._release), delay) def _release(self): """Releases the packet being sent to the receiving `Device` after the packet has traversed the link. Notes ----- This function dequeues the first packet in the buffer and begins sending it across the link. The packet is sent to its destination after delay time, where delay is the propagation delay of the link. Routing packets and acknowledgement packets are sent instantaneously to their destination without considering the propagation delay. This simplifies the network simulation. """ # Dequeue the first packet in the buffer packet, source_id, time = self._release_into_link_buffer.pop() self._send_rate.add_point(packet, self.env.time) # Update the buffer size self._size -= packet.size # Record the buffer size self.bw.record('{0}, {1}'.format(self.env.time, self._size), 'link_{0}.buffer'.format(self._id)) # Figure out which device to send to if (source_id == self._device_a.network_id): f = self._device_b.receive else: f = self._device_a.receive # Create the event message msg = "I am link {0}. I have sent " if packet.is_ack: msg += "ACK " msg += "packet {1}" # Ignore routing packet propagation so updates happen instantly. if packet.is_routing or packet.is_ack: delay = 0 else: delay = self._delay # Release to device after self._delay time self.env.add_event(Event(msg.format(self._id, packet.pack_id), self._id, f, packet=packet), delay) # Record link sent self.bw.record('{0}, {1}'.format(self.env.time, packet.size), 'link_{0}.sent'.format(self._id)) # Record the link rate for packets that are not acknowledgements or # routing packets if not packet.is_ack and not packet.is_routing: self.bw.record('{0}, {1}'.format(self.env.time, float(packet.size) / (self.env.time - time) / 1000.0), 'link_{0}.rate'.format(self._id)) # Process the next packet in the buffer if len(self._release_into_link_buffer) > 0: # Get the next packet in the buffer packet_info = self._release_into_link_buffer[-1] next_packet = packet_info[0] next_source_id = packet_info[1] # If the next packet's destination is not the same as the current # packet's destination and we are running in HALF_DUPLEX mode, wait # until the current packet has left the link before sending the # next packet. if next_source_id != source_id and HALF_DUPLEX: delay = self._delay else: delay = 0 # Create the event message msg = "I am link {0}. I am ready to send " if next_packet.is_ack: msg += "ACK " msg += "packet {1}" # Begin sending the next packet after delay time self.env.add_event(Event(msg.format(self._id, next_packet.pack_id), self._id, self._send), delay) def get_buffer_size(self): """Returns the buffer size in bits.""" total_size = 0 for packet, source_id, time in self._release_into_link_buffer: total_size += packet.size return total_size def measure_distance(self): """Measure the link distance. Sets the distance attribute of the link. """ if self.bw.static_routing: self._distance = self.delay else: self._distance = (self.delay + self.get_buffer_size() / float(self.rate))
class Router(Device): """Class for routers. Routers are responsible for initializing and updating their routing table, and sending packets based on their routing table. Parameters ---------- router_id : string A unique id for the router. Attributes ---------- network_id : string A unique id of the device in the network. links : list A list of links that the router is connected to. routing_table : dict A dictionary representing the router's routing table. new_routing_table : dict A dictionary representing the router's new routing table. env : `Network` The network that the link belongs to. bw : `Blackwidow` BlackWidow simulation object containing simulation settings. send_rate : Rate_Graph object Send rate graphing object. receive_rate : Rate_Graph object Receive rate graphing object. Methods ------- add_link(link) Adds a link to the router. send(packet) Sends a packet to a link. receive(packet) Receives a packet from a link. start_new_routing() Starts a new routing round. send_routing() Sends a routing packet to all neighbors. update_route() Update the new_routing_table based on routing packets. _distance(link) Gets the distance of a link. """ def __init__(self, router_id, env, bw): """Constructor for Router class.""" super(Router, self).__init__(router_id) self.env = env self.bw = bw self._routing_table = {} self._new_routing_table = {} self._send_rate = Rate_Graph(router_id, "router {0} send rate".format(router_id), self.env, self.bw) self._receive_rate = Rate_Graph( router_id, "router {0} receive" " rate".format(router_id), self.env, self.bw) self.env.add_event( Event("{} sent routing" " packet".format(self._network_id), self._network_id, self.start_new_routing), 0) def add_link(self, link): """Overrides Device.add_link() to add to routing table. Parameters ---------- link : Link The link to add to the router. """ self._links.append(link) network_id = link._device_a.network_id if (network_id == self._network_id): network_id = link._device_b.network_id self._routing_table[network_id] = { 'link': link, 'distance': self._distance(link) } self._new_routing_table[network_id] = \ {'link': link, 'distance': self._distance(link)} def send(self, packet): """Send packet to appropriate link. First looks in the new routing table to see if we know how to reach it there. Otherwise uses the old routing table. Parameters ---------- packet : Packet Packet to send through the router. """ route = None self._send_rate.add_point(packet, self.env.time) if packet.dest.network_id in self._new_routing_table: route = self._new_routing_table[packet.dest.network_id] elif packet.dest.network_id in self._routing_table: route = self._routing_table[packet.dest.network_id] if route is not None and 'link' in route: route['link'].receive(packet, self._network_id) def receive(self, packet): """Process packet by sending it out. If the packet is routing, calls update_route to update the new_routing_table. Parameters ---------- packet : Packet Received packet. """ self._receive_rate.add_point(packet, self.env.time) if packet.is_routing: self.update_route(packet) print "{} received routing packet from {}".format( self._network_id, packet.src) else: self.send(packet) def start_new_routing(self): """Start a new routing round. If there is dynamic routing, updates the routing table to the new routing table built up by dynamic routing and measures the distance for each link. """ # Reset routing table if dynamic routing. if not self.bw.static_routing: self._new_routing_table = {} for link in self._links: link.measure_distance() network_id = link._device_a.network_id if (network_id == self._network_id): network_id = link._device_b.network_id self._new_routing_table[network_id] = \ {'link': link, 'distance': self._distance(link)} self._routing_table = self._new_routing_table if self.env.time < 500: self.env.add_event( Event( "{} reset its routing" " table.".format(self._network_id), self._network_id, self.start_new_routing), 10) else: self.env.add_event( Event( "{} reset its routing" " table.".format(self._network_id), self._network_id, self.start_new_routing), 5000) self.send_routing() def send_routing(self): """Send routing packets to all neighbors.""" for link in self._links: other_device = link._device_a if (other_device.network_id == self._network_id): other_device = link.device_b if type(other_device) is Router: packet = RoutingPacket(ROUTING_PKT_ID, self._network_id, other_device.network_id, None, self._new_routing_table, self.bw.routing_packet_size) link.receive(packet, self._network_id) print "Sent routing packet from {}".format(self._network_id) def update_route(self, packet): """Update routing table. Goes through the routing table contained in the routing packet and determines if it contains a better way to get to each destination. This uses a distributed version of the Bellman-Ford algorithm. Parameters ---------- packet : Packet Routing packet to update the route. """ link = None if packet.src in self._new_routing_table: route = self._new_routing_table[packet.src] if 'link' in route: link = route['link'] else: raise ValueError('{} not found in {} \'s routing table.'.format( packet.src, self._network_id)) route_changed = False for dest, route in packet.routing_table.items(): distance = route['distance'] + link.distance if dest not in self._new_routing_table: self._new_routing_table[dest] = { 'link': link, 'distance': distance } route_changed = True elif distance < self._new_routing_table[dest]['distance']: self._new_routing_table[dest] = { 'link': link, 'distance': distance } route_changed = True if route_changed: self.send_routing() def _distance(self, link): """Get the distance of the link. Parameters ---------- link : Link Link to get distance of. """ distance = link.delay + link.get_buffer_size() / float(link.rate) if self.bw.static_routing: distance = link.delay return distance
class Flow(object): """Simple class for flows. Flows will trigger host behavior. Has slow start and congestion avoidance. Parameters ---------- flow_id : string A unique id for the flow. source : `Device` The source for the flow. destination : `Device` The destination for the flow. amount : int The amount of data to send in MB. env : `Network` The network that the flow belongs to. time : float The amount of time to wait before starting to send. Specified in ms. bw : Blackwidow The printer to print data to Attributes ---------- flow_id : string The flow id. src : `Device` The source for the flow. dest : `Device` The destination for the flow. amount : int The amount of data left to send in MB. env : `Network` The network that the flow belongs to. flow_start : float The amount of time to wait before starting to send. Specified in ms. pack_num : int The next pack_num to check to send. cwnd : float Congestion window size. ssthresh : float Slow start threshold resend_time : float ms before packets are sent after an ack receival min_RTT : float Minimum round trip time observed for this flow last_RTT : float Last round trip time observed for this flow SRTT : float Weighted average of round trip times biased towards recent RTT RTTVAR : float Variance of round trip times RTO : float Retransmission timeout in ms packets_sent : list List of packets that have been sent but haven't had their ack received packets_time_out : list List of packets that have exceeded timeout and need to be resent acks_arrived : set Set of ack packets that have been received done : int 0 if flow isn't finished; 1 if flow is finished Used to avoid decrementing flow more than once. send_rate : Rate_Graph Keeps track of the rate the flow is sending at and outputs to CSV file in real time. receive_rate : Rate_Graph Keeps track of the rate the flow is receiving at and outputs to CSV file in real time. """ def __init__(self, flow_id, source, destination, amount, env, time, bw): """ Constructor for Flow class """ self._flow_id = flow_id self._src = source self._dest = destination self._amount = amount * 8 * 10**6 self._pack_num = 0 self._cwnd = 1.0 self._ssthresh = 1000 self._resend_time = 10 self._min_RTT = 1000.0 self._last_RTT = 3000.0 self._SRTT = -1 self._RTTVAR = 0 self._RTO = 3000 self._packets_sent = [] self._packets_time_out = [] self._acks_arrived = set() self.env = env self.bw = bw self._flow_start = time * 1000.0 self._last_packet = 0 self._done = 0 self._send_rate = Rate_Graph(self._flow_id, "flow {0} send rate".format(self.flow_id), self.env, self.bw) self._receive_rate = Rate_Graph( self._flow_id, "flow {0} receive" " rate".format(self.flow_id), self.env, self.bw) self.env.add_event( Event("Start flow", self._flow_id, self.send_packet), self._flow_start) @property def flow_id(self): return self._flow_id @flow_id.setter def flow_id(self, value): raise AttributeError("Cannot change flow" " id: {0}".format(self._flow_id)) @property def src(self): return self._src @src.setter def src(self, value): raise AttributeError("Cannot change flow" " source: {0}".format(self._flow_id)) @property def dest(self): return self._dest @dest.setter def dest(self, value): raise AttributeError("Cannot change flow" " destination: {0}".format(self._flow_id)) @property def amount(self): return self._amount @amount.setter def amount(self, value): raise AttributeError("Cannot change flow" " amount: {0}".format(self._flow_id)) @property def flow_start(self): return self._flow_start @flow_start.setter def flow_start(self, value): raise AttributeError("Cannot change flow" " start: {0}".format(self._flow_id)) def __str__(self): msg = "Flow {0}, sending from {1} to {2}" return msg.format(self._flow_id, self._src.network_id, self._dest.network_id) def _send_ack(self, packet): """ Creates ack for packet. Parameters ---------- packet : `Packet` The packet to be received. """ if self._src == packet.src and self._dest == packet.dest: ack_packet = AckPacket(packet.pack_id, packet.dest, packet.src, self._flow_id, timestamp=packet.timestamp) self._dest.send(ack_packet) print "Flow sent ack packet {0}".format(packet.pack_id) else: print "Received wrong packet." def send_packet(self): """ Send a packet. """ if self._amount > 0: # Send packets up to the window size. while (len(self._packets_sent) - len(self._packets_time_out) < self._cwnd): pack = DataPacket(self._pack_num, self._src, self._dest, self._flow_id, timestamp=self.env.time) if (self._pack_num not in self._acks_arrived): self._src.send(pack) print "Flow sent packet {0}".format(pack.pack_id) self.bw.record('{0}, {1}'.format(self.env.time, pack.size), 'flow_{0}.sent'.format(self.flow_id)) self._send_rate.add_point(pack, self.env.time) self.env.add_event( Event("Timeout", self._flow_id, self._timeout, pack_num=self._pack_num), self._RTO) # Shouldn't subtract pack.size if sent before. if (self._pack_num not in self._packets_sent): self._amount = self._amount - pack.size self._packets_sent.append(self._pack_num) print "Flow has {0} bits left".format(self._amount) if self._pack_num in self._packets_time_out: self._packets_time_out.remove(self._pack_num) self._pack_num = self._pack_num + 1 if self._amount <= 0: break else: # Just keep resending last few packets until done while len(self._packets_time_out) > 0: self._pack_num = self._packets_time_out[0] pack = DataPacket(self._pack_num, self._src, self._dest, self._flow_id, timestamp=self.env.time) self._src.send(pack) self._send_rate.add_point(pack, self.env.time) self._packets_time_out.remove(self._pack_num) self.env.add_event( Event("Timeout", self._flow_id, self._timeout, pack_num=self._pack_num), self._RTO) def receive(self, packet): """ Generate an ack or respond to bad packet. Parameters ---------- packet : `Packet` The packet to be received. """ # Packet arrived at destination. Send ack. if packet.dest == self._dest: print "Flow received packet {0}".format(packet.pack_id) if packet.pack_id not in self._acks_arrived: self._send_ack(packet) # Ack arrived at source. Update window size. else: self._receive_ack(packet) def _receive_ack(self, packet): if packet.pack_id not in self._acks_arrived: self._respond_to_ack() self._update_RTT(packet) # Update lists by removing pack_id if packet.pack_id in self._packets_sent: self._packets_sent.remove(packet.pack_id) if packet.pack_id in self._packets_time_out: self._packets_time_out.remove(packet.pack_id) # Update which acks have arrived self._acks_arrived.add(packet.pack_id) print "Flow {} received ack for packet {}".format( self._flow_id, packet.pack_id) self.bw.record('{0}, {1}'.format(self.env.time, packet.size), 'flow_{0}.received'.format(self.flow_id)) self._receive_rate.add_point(packet, self.env.time) # Check if done if (len(self._packets_sent) == 0 and self._amount <= 0 and self._done == 0): self.env.decrement_flows() self._done = 1 def _respond_to_ack(self): """ Update window size. """ self.env.add_event(Event("Send", self._flow_id, self.send_packet), self._resend_time) if self._cwnd < self._ssthresh: self._cwnd = self._cwnd + 1.0 else: self._cwnd = self._cwnd + 1.0 / self._cwnd print "Flow {} window size is {}".format(self._flow_id, self._cwnd) self.bw.record('{0}, {1}'.format(self.env.time, self._cwnd), 'flow_{0}.window'.format(self.flow_id)) def _update_RTT(self, packet): """ Update last RTT and min RTT and retransmission timeout. Parameters ---------- packet : `Packet` The packet that was received. Need this to get the timestamp """ self._last_RTT = self.env.time - packet.timestamp if self._SRTT == -1: self._SRTT = self._last_RTT self._RTTVAR = self._last_RTT / 2.0 else: self._RTTVAR = ((1.0 - beta) * self._RTTVAR + beta * abs(self._SRTT - self._last_RTT)) self._SRTT = (1.0 - alpha) * self._SRTT + alpha * self._last_RTT self._RTO = min(max(self._SRTT + max(G, K * self._RTTVAR), 1000), 5000) print "RTO is {}".format(self._RTO) if self._last_RTT < self._min_RTT: self._min_RTT = self._last_RTT self.bw.record( '{0}, {1}'.format(self.env.time, self._last_RTT - self._min_RTT), 'flow_{0}.packet_delay'.format(self.flow_id)) def _timeout(self, pack_num): """ Generate an ack or respond to bad packet. Parameters ---------- pack_num : `Packet`number The packet number of the packet to check for timeout. """ if pack_num not in self._acks_arrived: self.env.add_event( Event("Resend", self._flow_id, self.send_packet), self._resend_time) # Go back n if pack_num not in self._packets_time_out: self._packets_time_out.append(pack_num) self._pack_num = pack_num self._reset_window() def _reset_window(self): """ Called when a packet timeout occurs. Sets ssthresh to max(2, cwnd/2) and cwnd to 1. """ if self._cwnd > 4: self._ssthresh = self._cwnd / float(2) else: self._ssthresh = 2.0 self._cwnd = 1.0 print "Flow {} window size is {}".format(self._flow_id, self._cwnd) self.bw.record('{0}, {1}'.format(self.env.time, self._cwnd), 'flow_{0}.window'.format(self.flow_id))
class Flow(object): """Simple class for flows. Flows will trigger host behavior. Has slow start and congestion avoidance. Parameters ---------- flow_id : string A unique id for the flow. source : `Device` The source for the flow. destination : `Device` The destination for the flow. amount : int The amount of data to send in MB. env : `Network` The network that the flow belongs to. time : float The amount of time to wait before starting to send. Specified in ms. bw : Blackwidow The printer to print data to Attributes ---------- flow_id : string The flow id. src : `Device` The source for the flow. dest : `Device` The destination for the flow. amount : int The amount of data left to send in MB. env : `Network` The network that the flow belongs to. flow_start : float The amount of time to wait before starting to send. Specified in ms. pack_num : int The next pack_num to check to send. cwnd : float Congestion window size. ssthresh : float Slow start threshold resend_time : float ms before packets are sent after an ack receival min_RTT : float Minimum round trip time observed for this flow last_RTT : float Last round trip time observed for this flow SRTT : float Weighted average of round trip times biased towards recent RTT RTTVAR : float Variance of round trip times RTO : float Retransmission timeout in ms packets_sent : list List of packets that have been sent but haven't had their ack received packets_time_out : list List of packets that have exceeded timeout and need to be resent acks_arrived : set Set of ack packets that have been received done : int 0 if flow isn't finished; 1 if flow is finished Used to avoid decrementing flow more than once. send_rate : Rate_Graph Keeps track of the rate the flow is sending at and outputs to CSV file in real time. receive_rate : Rate_Graph Keeps track of the rate the flow is receiving at and outputs to CSV file in real time. """ def __init__(self, flow_id, source, destination, amount, env, time, bw): """ Constructor for Flow class """ self._flow_id = flow_id self._src = source self._dest = destination self._amount = amount*8*10**6 self._pack_num = 0 self._cwnd = 1.0 self._ssthresh = 1000 self._resend_time = 10 self._min_RTT = 1000.0 self._last_RTT = 3000.0 self._SRTT = -1 self._RTTVAR = 0 self._RTO = 3000 self._packets_sent = [] self._packets_time_out = [] self._acks_arrived = set() self.env = env self.bw = bw self._flow_start = time*1000.0 self._last_packet = 0 self._done = 0 self._send_rate = Rate_Graph(self._flow_id, "flow {0} send rate".format(self.flow_id), self.env, self.bw) self._receive_rate = Rate_Graph(self._flow_id, "flow {0} receive" " rate".format(self.flow_id), self.env, self.bw) self.env.add_event(Event("Start flow", self._flow_id, self.send_packet), self._flow_start) @property def flow_id(self): return self._flow_id @flow_id.setter def flow_id(self, value): raise AttributeError("Cannot change flow" " id: {0}".format(self._flow_id)) @property def src(self): return self._src @src.setter def src(self, value): raise AttributeError("Cannot change flow" " source: {0}".format(self._flow_id)) @property def dest(self): return self._dest @dest.setter def dest(self, value): raise AttributeError("Cannot change flow" " destination: {0}".format(self._flow_id)) @property def amount(self): return self._amount @amount.setter def amount(self, value): raise AttributeError("Cannot change flow" " amount: {0}".format(self._flow_id)) @property def flow_start(self): return self._flow_start @flow_start.setter def flow_start(self, value): raise AttributeError("Cannot change flow" " start: {0}".format(self._flow_id)) def __str__(self): msg = "Flow {0}, sending from {1} to {2}" return msg.format(self._flow_id, self._src.network_id, self._dest.network_id) def _send_ack(self, packet): """ Creates ack for packet. Parameters ---------- packet : `Packet` The packet to be received. """ if self._src == packet.src and self._dest == packet.dest: ack_packet = AckPacket(packet.pack_id, packet.dest, packet.src, self._flow_id, timestamp=packet.timestamp) self._dest.send(ack_packet) print "Flow sent ack packet {0}".format(packet.pack_id) else: print "Received wrong packet." def send_packet(self): """ Send a packet. """ if self._amount > 0: # Send packets up to the window size. while (len(self._packets_sent) - len(self._packets_time_out) < self._cwnd): pack = DataPacket(self._pack_num, self._src, self._dest, self._flow_id, timestamp=self.env.time) if (self._pack_num not in self._acks_arrived): self._src.send(pack) print "Flow sent packet {0}".format(pack.pack_id) self.bw.record('{0}, {1}'.format(self.env.time, pack.size), 'flow_{0}.sent'.format(self.flow_id)) self._send_rate.add_point(pack, self.env.time) self.env.add_event(Event("Timeout", self._flow_id, self._timeout, pack_num=self._pack_num), self._RTO) # Shouldn't subtract pack.size if sent before. if (self._pack_num not in self._packets_sent): self._amount = self._amount - pack.size self._packets_sent.append(self._pack_num) print "Flow has {0} bits left".format(self._amount) if self._pack_num in self._packets_time_out: self._packets_time_out.remove(self._pack_num) self._pack_num = self._pack_num + 1 if self._amount <= 0: break else: # Just keep resending last few packets until done while len(self._packets_time_out) > 0: self._pack_num = self._packets_time_out[0] pack = DataPacket(self._pack_num, self._src, self._dest, self._flow_id, timestamp=self.env.time) self._src.send(pack) self._send_rate.add_point(pack, self.env.time) self._packets_time_out.remove(self._pack_num) self.env.add_event(Event("Timeout", self._flow_id, self._timeout, pack_num=self._pack_num), self._RTO) def receive(self, packet): """ Generate an ack or respond to bad packet. Parameters ---------- packet : `Packet` The packet to be received. """ # Packet arrived at destination. Send ack. if packet.dest == self._dest: print "Flow received packet {0}".format(packet.pack_id) if packet.pack_id not in self._acks_arrived: self._send_ack(packet) # Ack arrived at source. Update window size. else: self._receive_ack(packet) def _receive_ack(self, packet): if packet.pack_id not in self._acks_arrived: self._respond_to_ack() self._update_RTT(packet) # Update lists by removing pack_id if packet.pack_id in self._packets_sent: self._packets_sent.remove(packet.pack_id) if packet.pack_id in self._packets_time_out: self._packets_time_out.remove(packet.pack_id) # Update which acks have arrived self._acks_arrived.add(packet.pack_id) print "Flow {} received ack for packet {}".format(self._flow_id, packet.pack_id) self.bw.record('{0}, {1}'.format(self.env.time, packet.size), 'flow_{0}.received'.format(self.flow_id)) self._receive_rate.add_point(packet, self.env.time) # Check if done if (len(self._packets_sent) == 0 and self._amount <= 0 and self._done == 0): self.env.decrement_flows() self._done = 1 def _respond_to_ack(self): """ Update window size. """ self.env.add_event(Event("Send", self._flow_id, self.send_packet), self._resend_time) if self._cwnd < self._ssthresh: self._cwnd = self._cwnd + 1.0 else: self._cwnd = self._cwnd + 1.0/self._cwnd print "Flow {} window size is {}".format(self._flow_id, self._cwnd) self.bw.record('{0}, {1}'.format(self.env.time, self._cwnd), 'flow_{0}.window'.format(self.flow_id)) def _update_RTT(self, packet): """ Update last RTT and min RTT and retransmission timeout. Parameters ---------- packet : `Packet` The packet that was received. Need this to get the timestamp """ self._last_RTT = self.env.time - packet.timestamp if self._SRTT == -1: self._SRTT = self._last_RTT self._RTTVAR = self._last_RTT / 2.0 else: self._RTTVAR = ((1.0 - beta) * self._RTTVAR + beta * abs(self._SRTT - self._last_RTT)) self._SRTT = (1.0 - alpha)*self._SRTT + alpha*self._last_RTT self._RTO = min(max(self._SRTT + max(G, K*self._RTTVAR), 1000), 5000) print "RTO is {}".format(self._RTO) if self._last_RTT < self._min_RTT: self._min_RTT = self._last_RTT self.bw.record('{0}, {1}'.format(self.env.time, self._last_RTT - self._min_RTT), 'flow_{0}.packet_delay'.format(self.flow_id)) def _timeout(self, pack_num): """ Generate an ack or respond to bad packet. Parameters ---------- pack_num : `Packet`number The packet number of the packet to check for timeout. """ if pack_num not in self._acks_arrived: self.env.add_event(Event("Resend", self._flow_id, self.send_packet), self._resend_time) # Go back n if pack_num not in self._packets_time_out: self._packets_time_out.append(pack_num) self._pack_num = pack_num self._reset_window() def _reset_window(self): """ Called when a packet timeout occurs. Sets ssthresh to max(2, cwnd/2) and cwnd to 1. """ if self._cwnd > 4: self._ssthresh = self._cwnd / float(2) else: self._ssthresh = 2.0 self._cwnd = 1.0 print "Flow {} window size is {}".format(self._flow_id, self._cwnd) self.bw.record('{0}, {1}'.format(self.env.time, self._cwnd), 'flow_{0}.window'.format(self.flow_id))