Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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))
Ejemplo n.º 3
0
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))
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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))