class PTCProtocol(object): def __init__(self, alpha, beta, delay, dropChance, wait): self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.read_stream_open = True self.write_stream_open = True self.rto_estimator = RTOEstimator(self, alpha, beta, delay, dropChance) self.packet_handler = IncomingPacketHandler(self) self.delay = delay self.dropChance = dropChance self.wait = wait self.ticks = 0 self.retransmissions = 0 self.close_mode = NO_WAIT self.close_event = threading.Event() self.initialize_threads() self.initialize_timers() def initialize_threads(self): self.packet_sender = PacketSender(self) self.packet_receiver = PacketReceiver(self) self.clock = Clock(self) def initialize_timers(self): self.retransmission_timer = RetransmissionTimer(self) def start_threads(self): self.packet_receiver.start() self.packet_sender.start() self.clock.start() def stop_threads(self): self.packet_receiver.stop() self.packet_sender.stop() self.packet_sender.notify() self.clock.stop() def join_threads(self): self.packet_receiver.join() self.packet_sender.join() self.clock.join() def set_state(self, state): self.state = state if state == CLOSED or\ (self.close_mode == NO_WAIT and state == FIN_WAIT2): # Señalizar este evento cuando la conexión queda completamente # cerrada o bien cuando el usuario del socket explícitamente # eligió esperar a que el interlocutor también cierre. Por # defecto, el comportamiento es similar al de TCP (i.e., cierre # asimétrico). self.close_event.set() if state == ESTABLISHED: self.connected_event.set() def compute_iss(self): value = random.randint(0, MAX_SEQ/2) return SequenceNumber(value) def initialize_control_block_from(self, packet): # +1 dado que el SYN se secuencia. receive_seq = 1 + packet.get_seq_number() send_seq = 1 + self.iss send_window = packet.get_window_size() receive_window = self.rcv_wnd self.control_block = PTCControlBlock(send_seq, receive_seq, send_window, receive_window) def is_connected(self): connected_states = [ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK] return self.state in connected_states def build_packet(self, seq=None, ack=None, payload=None, flags=None, window=None): if seq is None: seq = self.control_block.get_snd_nxt() if flags is None: flags = [ACKFlag] if ack is None and ACKFlag in flags: ack = self.control_block.get_rcv_nxt() if window is None: window = self.control_block.get_rcv_wnd() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq, ack=ack, window=window) return packet def send_and_queue(self, packet, is_retransmission=False): if is_retransmission: # Algoritmo de Karn: no usar paquetes retransmitidos para # actualizar las estimaciones del RTO. if self.rto_estimator.is_tracking_packets(): tracked_packet = self.rto_estimator.get_tracked_packet() tracked_seq = tracked_packet.get_seq_number() if tracked_seq == packet.get_seq_number(): self.rto_estimator.untrack() else: # Sólo se hará seguimiento de paquetes frescos para estimar el # RTT (otra vez por el algoritmo de Karn). if not self.rto_estimator.is_tracking_packets(): self.rto_estimator.track(packet) # Encolar este paquete fresco para eventuales retransmisiones. # Las retransmisiones no se reencolan pues quedan al principio de # la cola hasta que son correctamente reconocidas. self.rqueue.put(packet) if not self.retransmission_timer.is_running(): # Usar la estimación actual del RTO para medir este paquete. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.start(current_rto) if self.wait and self.state == ESTABLISHED: if random.randint(0, 100) >= self.dropChance: time.sleep(self.delay * CLOCK_TICK) self.socket.send(packet) else: print '#DROP!' else: self.socket.send(packet) def set_destination_on_packet_builder(self, address, port): self.packet_builder.set_destination_address(address) self.packet_builder.set_destination_port(port) def bind(self, address, port): self.socket.bind(address, port) self.packet_builder.set_source_address(address) self.packet_builder.set_source_port(port) def listen(self): self.set_state(LISTEN) def connect_to(self, address, port): self.connected_event = threading.Event() self.set_destination_on_packet_builder(address, port) self.start_threads() syn_packet = self.build_packet(seq=self.iss, flags=[SYNFlag], window=self.rcv_wnd) self.set_state(SYN_SENT) self.send_and_queue(syn_packet) self.connected_event.wait() def accept(self): if self.state != LISTEN: raise PTCError('should listen first') self.connected_event = threading.Event() self.start_threads() # Esperar hasta que un cliente desee conectarse. self.connected_event.wait() def send(self, data): with self.control_block: if not self.write_stream_open: raise PTCError('write stream is closed') self.control_block.to_out_buffer(data) self.packet_sender.notify() def receive(self, size): data = self.control_block.from_in_buffer(size) updated_rcv_wnd = self.control_block.get_rcv_wnd() if updated_rcv_wnd > 0: wnd_packet = self.build_packet(window=updated_rcv_wnd) if self.wait: time.sleep(self.delay * CLOCK_TICK) self.socket.send(wnd_packet) return data def get_ticks(self): return self.ticks def tick(self): self.ticks += 1 self.retransmission_timer.tick() def acknowledge_packets_and_update_timers_with(self, packet): ack_number = packet.get_ack_number() if self.control_block.ack_is_accepted(ack_number): # Primero, pasar este paquete al estimador de RTO para que pueda # actualizar sus valores si corresponde (esto es, si el paquete # está reconociendo el paquete al que está haciendo seguimiento). self.rto_estimator.process_ack(packet) # Luego, desencolar paquetes para retransmitir y parar/reiniciar # el timer de retransmisiones según corresponda. self.remove_from_retransmission_queue_packets_acked_by(packet) def remove_from_retransmission_queue_packets_acked_by(self, packet): # Sólo ACKs mayores que SND_UNA y menores que SND_NXT son válidos # en este punto. with self.rqueue: snd_una = self.control_block.get_snd_una() snd_nxt = self.control_block.get_snd_nxt() # Ver qué paquetes encolados son totalmente reconocidos por # este paquete. Necesitamos SND_UNA y SND_NXT para poder # comparar correctamente los SEQs y ACKs. removed_packets = self.rqueue.remove_acknowledged_by(packet, snd_una, snd_nxt) if len(removed_packets) > 0: # Algunos paquetes se reconocieron, por lo que debemos # actualizar el timer de retransmisiones. self.adjust_retransmission_timer() def adjust_retransmission_timer(self): # Comenzar con cero retransmisiones para el próximo paquete. self.retransmissions = 0 if self.rqueue.empty(): # Todos los datos salientes quedaron reconocidos. Detener el timer. self.retransmission_timer.stop() else: # Algunos datos se reconocieron pero otros aún quedan. # Reiniciar el timer usando la estimación actual del RTO. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.restart(current_rto) def handle_outgoing(self): if self.control_block is None: # Cuando la conexión todavía no fue establecida, no tenemos nada # para enviar. return with self.control_block: # Analizar primero si tenemos un timeout de un paquete enviado. if self.retransmission_timer.has_expired() and\ not self.rqueue.empty(): # Si alcanzamos el máximo número permitido de retransmisiones, # liberar la conexión. if self.retransmissions >= MAX_RETRANSMISSION_ATTEMPTS: self.free() return # Limpiar la estimación del RTT si se hizo back-off reiteradas # veces. Posiblemente ya no represente el RTT real. if self.retransmissions > BOGUS_RTT_RETRANSMISSIONS: self.rto_estimator.clear_rtt() self.retransmissions += 1 # Hacer back-off del RTO y luego retransmitir el paquete más # viejo que aún no fue reconocido. self.rto_estimator.back_off_rto() packet = self.rqueue.head() self.send_and_queue(packet, is_retransmission=True) if self.write_stream_open or \ self.control_block.has_data_to_send(): self.attempt_to_send_data() else: # Mandar FIN cuando: # * El stream de escritura está cerrado, # * El estado es ESTABLISHED/CLOSE_WAIT # (i.e., todavía no se ha enviado un FIN), y # * Todo byte saliente fue exitosamente reconocido. self.attempt_to_send_FIN() def attempt_to_send_data(self): window_closed = False while self.control_block.has_data_to_send() and not window_closed: seq_number = self.control_block.get_snd_nxt() to_send = self.control_block.extract_from_out_buffer(MSS) if not to_send: # El bloque de control no devolvió nada, lo cual es un indicio # de que la ventana está cerrada. Luego, no tenemos nada más # por hacer hasta que lleguen los próximos ACKs. window_closed = True else: packet = self.build_packet(payload=to_send, seq=seq_number) self.send_and_queue(packet) def attempt_to_send_FIN(self): state_allows_closing = self.state in [ESTABLISHED, CLOSE_WAIT] if state_allows_closing and self.rqueue.empty(): fin_packet = self.build_packet(flags=[ACKFlag, FINFlag]) # Estamos enviando un FIN, y este flag se secuencia. Incrementar el # siguiente byte a enviar. self.control_block.increment_snd_nxt() new_state = FIN_WAIT1 if self.state == ESTABLISHED else LAST_ACK self.set_state(new_state) self.send_and_queue(fin_packet) def handle_incoming(self, packet): self.packet_handler.handle(packet) self.packet_sender.notify() def shutdown(self, how): if how == SHUT_RD: self.shutdown_read_stream() elif how == SHUT_WR: self.shutdown_write_stream() else: self.shutdown_read_stream() self.shutdown_write_stream() def shutdown_read_stream(self): self.read_stream_open = False def shutdown_write_stream(self): self.write_stream_open = False self.packet_sender.notify() def close(self, mode=NO_WAIT): self.close_mode = mode if self.state != CLOSED: self.shutdown(SHUT_RDWR) self.close_event.wait() self.free() self.join_threads() def free(self): if self.control_block is not None: self.control_block.flush_buffers() self.stop_threads() # En caso de que el establecimiento de conexión haya fallado, esto # destrabará al thread principal de la aplicación. self.connected_event.set() # Y, análogamente, esto destrabará al thread principal si se llama a # close y free es luego invocada por algún otro thread. self.close_event.set() self.set_state(CLOSED)
def __init__(self, ack_delay=0, ack_loss_probability=0, packet_loss_probability=0, alpha=0.8, beta=0.4, k=4, filepath='rto.dat'): self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability print 'self.ack_loss_probability: ' + str(self.ack_loss_probability) self.packet_loss_probability = packet_loss_probability self.beta = beta self.alpha = alpha self.k = k self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.rto_estimator = RTOEstimator(self) self.ticks = 0 self.retransmissions = 0 self.close_mode = NO_WAIT self.close_event = threading.Event() self.initialize_threads() self.initialize_timers() self.filepath = filepath self.retransmission_counter = 0
def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self)
def __init__(self, delay=0, perdida=0): self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self, delay, perdida) self.close_event = threading.Event() self.initialize_threads()
def __init__(self): self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.close_event = threading.Event() self.initialize_threads()
def __init__(self, ack_delay=0, ack_loss_probability=0): # Modificación self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability self.retransmissions = 0 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', filename='ptc.log') self.logger = logging.getLogger('PTCProtocol') self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete(protocol=self) self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.close_event = threading.Event() self.initialize_threads()
def __init__(self): self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.rto_estimator = RTOEstimator(self) self.ticks = 0 self.retransmissions = 0 self.close_mode = NO_WAIT self.close_event = threading.Event() self.initialize_threads() self.initialize_timers()
def __init__(self, ack_delay=0, ack_loss_probability=0): # Modificación self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability self.retransmissions = 0 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', filename='ptc.log') self.logger = logging.getLogger('PTCProtocol') self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete(protocol=self) self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.close_event = threading.Event() self.initialize_threads()
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() if payload is not None: self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) seq_number = packet.get_seq_number() if seq_number in self.retransmission_attempts: if self.retransmission_attempts[seq_number] > \ MAX_RETRANSMISSION_ATTEMPTS + 1: self.retransmission_attempts[seq_number] = 0 else: self.retransmission_attempts[seq_number] = 0 def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.set_destination_address(address) self.control_block.set_destination_port(port) syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # (1) Obtener los paquetes en self.retranmission_queue # (2) Volver a enviarlos # (3) Reencolarlos para otra eventual retransmisión # ...y verificar que no se exceda la cantidad máxima de reenvíos! # (hacer self.shutdown() si esto ocurre y dejar un mensaje en self.error) packet_list = [] for packet in self.retransmission_queue: packet_list.append(packet) self.retransmission_queue.clear() for packet in packet_list: if self.retransmission_attempts[packet.get_seq_number()] \ <= MAX_RETRANSMISSION_ATTEMPTS: packet.add_flag(RSTFlag) # Uso este flag sin utilidad para poder diferenciar retransmiciones self.send_and_queue_packet(packet) self.retransmission_attempts[packet.get_seq_number()] += 1 else: self.shutdown() self.error = 'Muchas retransmisiones, fuiste!' print "Muchas retransmisiones, fuiste!" def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # * Distinguir el caso donde el estado es SYN_SENT # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # * Ajustar la ventana deslizante con #ACK # * Tener en cuenta también el caso donde el estado es FIN_SENT if ACKFlag in packet: ack_number = packet.get_ack_number() seq_number = packet.get_seq_number() if self.state == SYN_SENT: if ack_number == self.control_block.send_seq: self.retransmission_queue.acknowledge(packet) self.control_block.increment_send_seq() self.control_block.update_send_window(ack_number) self.state = ESTABLISHED self.connected_event.set() elif self.state == FIN_SENT: if ack_number == self.control_block.send_seq: self.retransmission_queue.acknowledge(packet) self.state = CLOSED else: if self.control_block.ack_ok(ack_number): self.retransmission_queue.acknowledge(packet) self.control_block.update_send_window(ack_number) def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
def __init__(self, address, port): self.state = CLOSED self.control_block = ServerControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) self.connection_closed_event = threading.Event()
class PTCServerProtocol(object): def __init__(self, address, port): self.state = CLOSED self.control_block = ServerControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) self.connection_closed_event = threading.Event() def is_connected(self): return self.state == ESTABLISHED def is_closed(self): return self.state == CLOSED def build_packet(self, flags=None): ack = self.control_block.get_receive_seq() packet = self.packet_builder.build(flags=flags, ack=ack) return packet def send_packet(self, packet): self.socket.send(packet) def receive(self, size): if self.is_closed() and self.incoming_buffer.empty(): raise Exception('cannot receive: connection ended and no more pending data buffered') elif self.incoming_buffer.empty() and not self.is_connected(): raise Exception('cannot receive: connection not established') if size < MIN_PACKET_SIZE: size = MIN_PACKET_SIZE data = self.incoming_buffer.sync_get(MIN_PACKET_SIZE, size) return data def accept(self): self.incoming_buffer = DataBuffer() self.state = CLOSED self.worker = ServerProtocolWorker.spawn_for(self) self.worker.start() # No hay mucho por hacer... simplemente esperar a que caiga el SYN del cliente self.connected_event = threading.Event() self.connected_event.wait() def handle_incoming(self, packet): seq_number = packet.get_seq_number() if DEBUG: print 'recibi el paquete numero', seq_number if SYNFlag in packet: self.control_block.set_destination_address(packet.get_source_ip()) self.control_block.set_destination_port(packet.get_source_port()) self.control_block.set_receive_seq(seq_number) ack_packet = self.build_packet(flags=[ACKFlag]) self.send_packet(ack_packet) self.control_block.set_receive_seq(seq_number) self.control_block.increment_receive_seq() self.state = ESTABLISHED self.connected_event.set() if DEBUG: print 'server established' if FINFlag in packet: if self.control_block.accepts(seq_number): if DEBUG: print 'server ended' self.state = FIN_RECEIVED self.send_ack() self.close() else: data = packet.get_payload() if DEBUG: print 'recibí un paquete con esta data:', data if self.control_block.accepts(seq_number) and data: self.incoming_buffer.put(data) if self.state != FIN_RECEIVED: self.send_ack() self.control_block.increment_receive_seq() def send_ack(self): ack_packet = self.build_packet(flags=[ACKFlag]) self.send_packet(ack_packet) def shutdown(self): self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() def close(self): self.state = CLOSED self.connection_closed_event.set() def wait_for_close(self): self.connection_closed_event.wait()
class PTCProtocol(object): def __init__(self): self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.rto_estimator = RTOEstimator(self) self.ticks = 0 self.retransmissions = 0 self.close_mode = NO_WAIT self.close_event = threading.Event() self.initialize_threads() self.initialize_timers() def initialize_threads(self): self.packet_sender = PacketSender(self) self.packet_receiver = PacketReceiver(self) self.clock = Clock(self) def initialize_timers(self): self.retransmission_timer = RetransmissionTimer(self) def start_threads(self): self.packet_receiver.start() self.packet_sender.start() self.clock.start() def stop_threads(self): self.packet_receiver.stop() self.packet_sender.stop() self.packet_sender.notify() self.clock.stop() def join_threads(self): self.packet_receiver.join() self.packet_sender.join() self.clock.join() def set_state(self, state): self.state = state if state == CLOSED or\ (self.close_mode == NO_WAIT and state == FIN_WAIT2): # Signal this event when the connection is completely closed or # otherwise if the user explicitly chose to wait for the other # party to close also. By default, this behavior resembles TCP # (i.e., asymmetric close). self.close_event.set() if state == ESTABLISHED: self.connected_event.set() def compute_iss(self): value = random.randint(0, MAX_SEQ) return SequenceNumber(value) def initialize_control_block_from(self, packet): # +1 since the SYN flag is also sequenced. receive_seq = 1 + packet.get_seq_number() send_seq = 1 + self.iss send_window = packet.get_window_size() receive_window = self.rcv_wnd self.control_block = PTCControlBlock(send_seq, receive_seq, send_window, receive_window) def is_connected(self): connected_states = [ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK] return self.state in connected_states def build_packet(self, seq=None, ack=None, payload=None, flags=None, window=None): if seq is None: seq = self.control_block.get_snd_nxt() if flags is None: flags = [ACKFlag] if ack is None and ACKFlag in flags: ack = self.control_block.get_rcv_nxt() if window is None: window = self.control_block.get_rcv_wnd() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq, ack=ack, window=window) return packet def send_and_queue(self, packet, is_retransmission=False): if is_retransmission: # Karn's algorithm: do not use retransmitted packets to update # RTO estimations. if self.rto_estimator.is_tracking_packets(): tracked_packet = self.rto_estimator.get_tracked_packet() tracked_seq = tracked_packet.get_seq_number() if tracked_seq == packet.get_seq_number(): self.rto_estimator.untrack() else: # Only fresh packets will be tracked for their RTTs (Karn's # algorithm once more). if not self.rto_estimator.is_tracking_packets(): self.rto_estimator.track(packet) # Enqueue this fresh packet for eventual retransmissions. # Retransmissions are not re-enqueued since they remain at the # head of the queue until they are acknowledged. self.rqueue.put(packet) if not self.retransmission_timer.is_running(): # Use current RTO estimation to time this packet. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.start(current_rto) self.socket.send(packet) def set_destination_on_packet_builder(self, address, port): self.packet_builder.set_destination_address(address) self.packet_builder.set_destination_port(port) def bind(self, address, port): self.socket.bind(address, port) self.packet_builder.set_source_address(address) self.packet_builder.set_source_port(port) def listen(self): self.set_state(LISTEN) def connect_to(self, address, port): self.connected_event = threading.Event() self.set_destination_on_packet_builder(address, port) self.start_threads() syn_packet = self.build_packet(seq=self.iss, flags=[SYNFlag], window=self.rcv_wnd) self.set_state(SYN_SENT) self.send_and_queue(syn_packet) self.connected_event.wait() def accept(self): if self.state != LISTEN: raise PTCError('should listen first') self.connected_event = threading.Event() self.start_threads() # Wait until client attempts to connect. self.connected_event.wait() def send(self, data): with self.control_block: if not self.write_stream_open: raise PTCError('write stream is closed') self.control_block.to_out_buffer(data) self.packet_sender.notify() def receive(self, size): data = self.control_block.from_in_buffer(size) updated_rcv_wnd = self.control_block.get_rcv_wnd() if updated_rcv_wnd > 0: wnd_packet = self.build_packet(window=updated_rcv_wnd) self.socket.send(wnd_packet) return data def get_ticks(self): return self.ticks def tick(self): self.ticks += 1 self.retransmission_timer.tick() def acknowledge_packets_and_update_timers_with(self, packet): ack_number = packet.get_ack_number() if self.control_block.ack_is_accepted(ack_number): # First, pass this packet to the RTO estimator in order to update # its values if appropriate (that is, if the packet is acknowledging # the packet tracked by it). self.rto_estimator.process_ack(packet) # Then, remove enqueued packets and stop/restart the retransmission # timer as required. self.remove_from_retransmission_queue_packets_acked_by(packet) def remove_from_retransmission_queue_packets_acked_by(self, packet): # Only ACK numbers greater than SND_UNA and less than SND_NXT are # valid here. with self.rqueue: snd_una = self.control_block.get_snd_una() snd_nxt = self.control_block.get_snd_nxt() # See which packets already enqueued are acknowledged by this # packet. SND_UNA and SND_NXT are needed for properly comparing # SEQs and ACKs. removed_packets = self.rqueue.remove_acknowledged_by(packet, snd_una, snd_nxt) if len(removed_packets) > 0: # Some packets were acked, and so we must update the # retransmission timer accordingly. self.adjust_retransmission_timer() def adjust_retransmission_timer(self): # Start at zero retransmissions for next packet. self.retransmissions = 0 if self.rqueue.empty(): # All outstanding data was acknowledged. Stop timer. self.retransmission_timer.stop() else: # Some data was acknowledged, but some still remains. # Restart the timer using current RTO estimation. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.restart(current_rto) def handle_outgoing(self): if self.control_block is None: # When connection is still not established, we don't have # anything to send. return with self.control_block: # See first if we have a transmission timeout. if self.retransmission_timer.has_expired() and\ not self.rqueue.empty(): # If we reached the maximum retransmissions allowed, # release the connection. if self.retransmissions >= MAX_RETRANSMISSION_ATTEMPTS: self.free() return # Clear RTT estimation; it was backed off several times and so # it might no longer represent the actual RTT. if self.retransmissions > BOGUS_RTT_RETRANSMISSIONS: self.rto_estimator.clear_rtt() self.retransmissions += 1 # Back off RTO and then retransmit the earliest packet not yet # acknowledged. self.rto_estimator.back_off_rto() packet = self.rqueue.head() self.send_and_queue(packet, is_retransmission=True) if self.write_stream_open or \ self.control_block.has_data_to_send(): self.attempt_to_send_data() else: # Send FIN when: # * Write stream is closed, # * State is ESTABLISHED/CLOSE_WAIT # (i.e., FIN was not yet sent), and # * Every outgoing byte was successfully acknowledged. self.attempt_to_send_FIN() def attempt_to_send_data(self): window_closed = False while self.control_block.has_data_to_send() and not window_closed: seq_number = self.control_block.get_snd_nxt() to_send = self.control_block.extract_from_out_buffer(MSS) if not to_send: # Control block returned nothing, which hints that the window # is closed. Thus, we have nothing else to do until further # ACKs arrive. window_closed = True else: packet = self.build_packet(payload=to_send, seq=seq_number) self.send_and_queue(packet) def attempt_to_send_FIN(self): state_allows_closing = self.state in [ESTABLISHED, CLOSE_WAIT] if state_allows_closing and self.rqueue.empty(): fin_packet = self.build_packet(flags=[ACKFlag, FINFlag]) # We are sending a FIN packet, and this flag is sequenced. Move # forward the next byte sequence to be sent. self.control_block.increment_snd_nxt() new_state = FIN_WAIT1 if self.state == ESTABLISHED else LAST_ACK self.set_state(new_state) self.send_and_queue(fin_packet) def handle_incoming(self, packet): self.packet_handler.handle(packet) self.packet_sender.notify() def shutdown(self, how): if how == SHUT_RD: self.shutdown_read_stream() elif how == SHUT_WR: self.shutdown_write_stream() else: self.shutdown_read_stream() self.shutdown_write_stream() def shutdown_read_stream(self): self.read_stream_open = False def shutdown_write_stream(self): self.write_stream_open = False self.packet_sender.notify() def close(self, mode=NO_WAIT): self.close_mode = mode if self.state != CLOSED: self.shutdown(SHUT_RDWR) self.close_event.wait() self.free() self.join_threads() def free(self): if self.control_block is not None: self.control_block.flush_buffers() self.stop_threads() # In case connection establishment failed, this will unlock the main # thread. self.connected_event.set() # And, similarly, this will unlock the main thread if close is called # and free is later invoked by some other thread, for whatever reason. self.close_event.set() self.set_state(CLOSED)
class PTCProtocol(object): def __init__(self, ack_delay=0, ack_loss_probability=0): # Modificación self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability self.retransmissions = 0 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', filename='ptc.log') self.logger = logging.getLogger('PTCProtocol') self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete(protocol=self) self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.close_event = threading.Event() self.initialize_threads() def initialize_threads(self): self.packet_sender = PacketSender(self) self.packet_receiver = PacketReceiver(self) self.clock = Clock(self) self.keepalive_sender = KeepAliveSender(self) def start_threads(self): self.packet_receiver.start() self.packet_sender.start() self.clock.start() self.keepalive_sender.start() def stop_threads(self): self.packet_receiver.stop() self.packet_sender.stop() self.packet_sender.notify() self.clock.stop() self.keepalive_sender.stop() def join_threads(self): self.packet_receiver.join() self.packet_sender.join() self.clock.join() self.keepalive_sender.join() def set_state(self, state): self.state = state if state == CLOSED or state == FIN_WAIT2: self.close_event.set() if state == ESTABLISHED: self.connected_event.set() def compute_iss(self): value = random.randint(0, MAX_SEQ) return SequenceNumber(value) def initialize_control_block_from(self, packet): # +1 dado que el SYN se secuencia. receive_seq = 1 + packet.get_seq_number() send_seq = 1 + self.iss send_window = packet.get_window_size() receive_window = self.rcv_wnd self.control_block = PTCControlBlock(send_seq, receive_seq, send_window, receive_window) def is_connected(self): connected_states = [ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK] return self.state in connected_states def build_packet(self, seq=None, ack=None, payload=None, flags=None, window=None): if seq is None: seq = self.control_block.get_snd_nxt() if flags is None: flags = [ACKFlag] if ack is None and ACKFlag in flags: ack = self.control_block.get_rcv_nxt() if window is None: window = self.control_block.get_rcv_wnd() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq, ack=ack, window=window) return packet def send_and_queue(self, packet): self.rqueue.put(packet) self.socket.send(packet) def set_destination_on_packet_builder(self, address, port): self.packet_builder.set_destination_address(address) self.packet_builder.set_destination_port(port) def bind(self, address, port): self.socket.bind(address, port) self.packet_builder.set_source_address(address) self.packet_builder.set_source_port(port) def listen(self): self.set_state(LISTEN) def connect_to(self, address, port): self.connected_event = threading.Event() self.set_destination_on_packet_builder(address, port) self.start_threads() syn_packet = self.build_packet(seq=self.iss, flags=[SYNFlag], window=self.rcv_wnd) self.set_state(SYN_SENT) self.send_and_queue(syn_packet) self.connected_event.wait() def accept(self): if self.state != LISTEN: raise PTCError('should listen first') self.connected_event = threading.Event() self.start_threads() # Esperar hasta que un cliente desee conectarse. self.connected_event.wait() def send(self, data): with self.control_block: if not self.write_stream_open: raise PTCError('write stream is closed') self.control_block.to_out_buffer(data) self.packet_sender.notify() def receive(self, size): data = self.control_block.from_in_buffer(size) updated_rcv_wnd = self.control_block.get_rcv_wnd() if updated_rcv_wnd > 0: wnd_packet = self.build_packet(window=updated_rcv_wnd) self.logger.debug('Enviando window: window=%d' % updated_rcv_wnd) self.socket.send(wnd_packet) return data def tick(self): with self.rqueue: self.rqueue.tick() self.retransmit_packets_if_needed() def send_keepalive(self): rcv_wnd = self.control_block.get_rcv_wnd() packet = self.build_packet(window=rcv_wnd) self.logger.debug('Enviando keepalive: window=%d' % rcv_wnd) self.socket.send(packet) def retransmit_packets_if_needed(self): to_retransmit = self.rqueue.get_packets_to_retransmit() for packet in to_retransmit: attempts = self.update_retransmission_attempts_for(packet) self.logger.debug('Retransmitiendo: intento %d' % attempts) if attempts > MAX_RETRANSMISSION_ATTEMPTS: self.logger.debug('Límite de retransmisiones alcanzado') # Nos damos por vencidos. Se superó el máximo número de # retransmisiones para este paquete. self.free() else: self.retransmissions += 1 self.send_and_queue(packet) def update_retransmission_attempts_for(self, packet): seq_number = packet.get_seq_number() attempts = 1 + self.retransmission_attempts.setdefault(seq_number, 0) self.retransmission_attempts[seq_number] = attempts return attempts def acknowledge_packets_on_retransmission_queue_with(self, packet): ack_number = packet.get_ack_number() if self.control_block.ack_is_accepted(ack_number): # Sólo ACKs mayores que SND_UNA y menores que SND_NXT son válidos # en este punto. with self.rqueue: snd_una = self.control_block.get_snd_una() snd_nxt = self.control_block.get_snd_nxt() # Ver qué paquetes encolados son totalmente reconocidos por # este paquete. Necesitamos SND_UNA y SND_NXT para poder # comparar correctamente los SEQs y ACKs. removed_packets = self.rqueue.remove_acknowledged_by(packet, snd_una, snd_nxt) for removed_packet in removed_packets: seq_number = removed_packet.get_seq_number() if seq_number in self.retransmission_attempts: del self.retransmission_attempts[seq_number] def handle_outgoing(self): if self.control_block is None: # Cuando la conexión todavía no fue establecida, no tenemos nada # para enviar. return with self.control_block: if self.write_stream_open or self.control_block.has_data_to_send(): self.attempt_to_send_data() else: # Mandar FIN cuando: # * El stream de escritura está cerrado, # * El estado es ESTABLISHED/CLOSE_WAIT # (i.e., todavía no se ha enviado un FIN), y # * Todo byte saliente fue exitosamente reconocido. self.attempt_to_send_FIN() def attempt_to_send_data(self): window_closed = False while self.control_block.has_data_to_send() and not window_closed: seq_number = self.control_block.get_snd_nxt() to_send = self.control_block.extract_from_out_buffer(MSS) if not to_send: # El bloque de control no devolvió nada, lo cual es un indicio # de que la ventana está cerrada. Luego, no tenemos nada más # por hacer hasta que lleguen los próximos ACKs. window_closed = True else: packet = self.build_packet(payload=to_send, seq=seq_number) self.send_and_queue(packet) def attempt_to_send_FIN(self): state_allows_closing = self.state in [ESTABLISHED, CLOSE_WAIT] if state_allows_closing and self.rqueue.empty(): fin_packet = self.build_packet(flags=[ACKFlag, FINFlag]) # Estamos enviando un FIN, y este flag se secuencia. Incrementar el # siguiente byte a enviar. self.control_block.increment_snd_nxt() new_state = FIN_WAIT1 if self.state == ESTABLISHED else LAST_ACK self.set_state(new_state) self.send_and_queue(fin_packet) def handle_incoming(self, packet): self.packet_handler.handle(packet) self.packet_sender.notify() def shutdown(self, how): if how == SHUT_RD: self.shutdown_read_stream() elif how == SHUT_WR: self.shutdown_write_stream() else: self.shutdown_read_stream() self.shutdown_write_stream() def shutdown_read_stream(self): self.read_stream_open = False def shutdown_write_stream(self): self.write_stream_open = False self.packet_sender.notify() def close(self): if self.state != CLOSED: self.shutdown(SHUT_RDWR) self.close_event.wait() self.free() self.join_threads() def free(self): if self.control_block is not None: self.control_block.flush_buffers() self.stop_threads() # En caso de que el establecimiento de conexión haya fallado, esto # destrabará al thread principal de la aplicación. self.connected_event.set() # Y, análogamente, esto destrabará al thread principal si se llama a # close y free es luego invocada por algún otro thread. self.close_event.set() self.set_state(CLOSED)
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() if payload is not None: self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.set_destination_address(address) # No tendria que ser el protocol block ? self.control_block.set_destination_port(port) # No tendria que ser el protocol block ? syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # (1) Obtener los paquetes en self.retranmission_queue # (2) Volver a enviarlos # (3) Reencolarlos para otra eventual retransmisión # ...y verificar que no se exceda la cantidad máxima de reenvíos! # (hacer self.shutdown() si esto ocurre y dejar un mensaje en self.error) haypaquetesret = len(self.retransmission_queue.packets()) > 0 if haypaquetesret: print "Retransmit" self.retransmission_queue.__iter__() primer_paquete = self.retransmission_queue.next().get_seq_number() if self.retransmission_attempts.get(primer_paquete,0) >= MAX_RETRANSMISSION_ATTEMPTS: self.error = "Error muchas retransmisiones" self.shutdown() else: copia_retransmission_queue = self.retransmission_queue.packets() self.retransmission_queue.clear() for pack in copia_retransmission_queue: self.send_and_queue_packet(pack) self.retransmission_attempts[pack.get_seq_number()] = self.retransmission_attempts.get(pack.get_seq_number(),0) + 1 # Le sumo uno a su cantidad de retransmisiones, inicilizandolo en cero si no existe def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # * Distinguir el caso donde el estado es SYN_SENT # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # * Ajustar la ventana deslizante con #ACK # * Tener en cuenta también el caso donde el estado es FIN_SENT ack_num = packet.get_ack_number() if ACKFlag in packet and self.control_block.valid_ack(ack_num): # si hay ack y es valido self.retransmission_queue.acknowledge(packet) # lo sacamos del retransmission queue self.control_block.update_window(ack_num) # actualizamos la ventana for packs_ack in range(self.control_block.get_window_lo(),ack_num+1): # Eliminamos los paquetes ackeados del diccionario de intentos de retransmision del retransmission_attempts[packs_ack] if self.state == SYN_SENT: # Si estaba desconectado, conectate self.state = ESTABLISHED self.connected_event.set() elif self.state == FIN_SENT: # Si te reconoce el fin de la conexion, terminala self.state = CLOSED self.retransmission_queue.clear() self.retransmission_attempts.clear() self.close() #~ if self.outgoing_buffer and self.retransmission_queue.empty(): # Si recibimos el ack de lo ultimo imprimimos cuanto tardo #~ print clock() - start_time def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
class PTCProtocol(object): def __init__(self, ack_delay=0, ack_loss_probability=0, packet_loss_probability=0, alpha=0.8, beta=0.4, k=4, filepath='rto.dat'): self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability print 'self.ack_loss_probability: ' + str(self.ack_loss_probability) self.packet_loss_probability = packet_loss_probability self.beta = beta self.alpha = alpha self.k = k self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete() self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.rto_estimator = RTOEstimator(self) self.ticks = 0 self.retransmissions = 0 self.close_mode = NO_WAIT self.close_event = threading.Event() self.initialize_threads() self.initialize_timers() self.filepath = filepath self.retransmission_counter = 0 def initialize_threads(self): self.packet_sender = PacketSender(self) self.packet_receiver = PacketReceiver(self) self.clock = Clock(self) def initialize_timers(self): self.retransmission_timer = RetransmissionTimer(self) def start_threads(self): self.packet_receiver.start() self.packet_sender.start() self.clock.start() def stop_threads(self): self.packet_receiver.stop() self.packet_sender.stop() self.packet_sender.notify() self.clock.stop() def join_threads(self): self.packet_receiver.join() self.packet_sender.join() self.clock.join() def set_state(self, state): self.state = state if state == CLOSED or\ (self.close_mode == NO_WAIT and state == FIN_WAIT2): # Señalizar este evento cuando la conexión queda completamente # cerrada o bien cuando el usuario del socket explícitamente # eligió esperar a que el interlocutor también cierre. Por # defecto, el comportamiento es similar al de TCP (i.e., cierre # asimétrico). self.close_event.set() if state == ESTABLISHED: self.connected_event.set() def compute_iss(self): value = random.randint(0, MAX_SEQ / 2) return SequenceNumber(value) def initialize_control_block_from(self, packet): # +1 dado que el SYN se secuencia. receive_seq = 1 + packet.get_seq_number() send_seq = 1 + self.iss send_window = packet.get_window_size() receive_window = self.rcv_wnd self.control_block = PTCControlBlock(send_seq, receive_seq, send_window, receive_window) def is_connected(self): connected_states = [ ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK ] return self.state in connected_states def build_packet(self, seq=None, ack=None, payload=None, flags=None, window=None): if seq is None: seq = self.control_block.get_snd_nxt() if flags is None: flags = [ACKFlag] if ack is None and ACKFlag in flags: ack = self.control_block.get_rcv_nxt() if window is None: window = self.control_block.get_rcv_wnd() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq, ack=ack, window=window) return packet def send_and_queue(self, packet, is_retransmission=False): if is_retransmission: self.retransmission_counter = self.retransmission_counter + 1 # Algoritmo de Karn: no usar paquetes retransmitidos para # actualizar las estimaciones del RTO. if self.rto_estimator.is_tracking_packets(): tracked_packet = self.rto_estimator.get_tracked_packet() tracked_seq = tracked_packet.get_seq_number() if tracked_seq == packet.get_seq_number(): self.rto_estimator.untrack() else: # Sólo se hará seguimiento de paquetes frescos para estimar el # RTT (otra vez por el algoritmo de Karn). if not self.rto_estimator.is_tracking_packets(): self.rto_estimator.track(packet) # Encolar este paquete fresco para eventuales retransmisiones. # Las retransmisiones no se reencolan pues quedan al principio de # la cola hasta que son correctamente reconocidas. self.rqueue.put(packet) if not self.retransmission_timer.is_running(): # Usar la estimación actual del RTO para medir este paquete. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.start(current_rto) if self.state == ESTABLISHED and FINFlag not in packet: if random.uniform(0, 1) < self.packet_loss_probability: return self.socket.send(packet) def set_destination_on_packet_builder(self, address, port): self.packet_builder.set_destination_address(address) self.packet_builder.set_destination_port(port) def bind(self, address, port): self.socket.bind(address, port) self.packet_builder.set_source_address(address) self.packet_builder.set_source_port(port) def listen(self): self.set_state(LISTEN) def connect_to(self, address, port): self.connected_event = threading.Event() self.set_destination_on_packet_builder(address, port) self.start_threads() syn_packet = self.build_packet(seq=self.iss, flags=[SYNFlag], window=self.rcv_wnd) self.set_state(SYN_SENT) self.send_and_queue(syn_packet) self.connected_event.wait() def accept(self): if self.state != LISTEN: raise PTCError('should listen first') self.connected_event = threading.Event() self.start_threads() # Esperar hasta que un cliente desee conectarse. self.connected_event.wait() def send(self, data): with self.control_block: if not self.write_stream_open: raise PTCError('write stream is closed') self.control_block.to_out_buffer(data) self.packet_sender.notify() def receive(self, size): data = self.control_block.from_in_buffer(size) updated_rcv_wnd = self.control_block.get_rcv_wnd() if updated_rcv_wnd > 0: wnd_packet = self.build_packet(window=updated_rcv_wnd) self.socket.send(wnd_packet) return data def get_ticks(self): return self.ticks def tick(self): self.ticks += 1 self.retransmission_timer.tick() def acknowledge_packets_and_update_timers_with(self, packet): ack_number = packet.get_ack_number() if self.control_block.ack_is_accepted(ack_number): # Primero, pasar este paquete al estimador de RTO para que pueda # actualizar sus valores si corresponde (esto es, si el paquete # está reconociendo el paquete al que está haciendo seguimiento). self.rto_estimator.process_ack(packet) # Luego, desencolar paquetes para retransmitir y parar/reiniciar # el timer de retransmisiones según corresponda. self.remove_from_retransmission_queue_packets_acked_by(packet) def remove_from_retransmission_queue_packets_acked_by(self, packet): # Sólo ACKs mayores que SND_UNA y menores que SND_NXT son válidos # en este punto. with self.rqueue: snd_una = self.control_block.get_snd_una() snd_nxt = self.control_block.get_snd_nxt() # Ver qué paquetes encolados son totalmente reconocidos por # este paquete. Necesitamos SND_UNA y SND_NXT para poder # comparar correctamente los SEQs y ACKs. removed_packets = self.rqueue.remove_acknowledged_by( packet, snd_una, snd_nxt) if len(removed_packets) > 0: # Algunos paquetes se reconocieron, por lo que debemos # actualizar el timer de retransmisiones. self.adjust_retransmission_timer() def adjust_retransmission_timer(self): # Comenzar con cero retransmisiones para el próximo paquete. self.retransmissions = 0 if self.rqueue.empty(): # Todos los datos salientes quedaron reconocidos. Detener el timer. self.retransmission_timer.stop() else: # Algunos datos se reconocieron pero otros aún quedan. # Reiniciar el timer usando la estimación actual del RTO. current_rto = self.rto_estimator.get_current_rto() self.retransmission_timer.restart(current_rto) def handle_outgoing(self): if self.control_block is None: # Cuando la conexión todavía no fue establecida, no tenemos nada # para enviar. return with self.control_block: # Analizar primero si tenemos un timeout de un paquete enviado. if self.retransmission_timer.has_expired() and\ not self.rqueue.empty(): # Si alcanzamos el máximo número permitido de retransmisiones, # liberar la conexión. if self.retransmissions >= MAX_RETRANSMISSION_ATTEMPTS: self.free() return # Limpiar la estimación del RTT si se hizo back-off reiteradas # veces. Posiblemente ya no represente el RTT real. if self.retransmissions > BOGUS_RTT_RETRANSMISSIONS: self.rto_estimator.clear_rtt() self.retransmissions += 1 # Hacer back-off del RTO y luego retransmitir el paquete más # viejo que aún no fue reconocido. self.rto_estimator.back_off_rto() packet = self.rqueue.head() self.send_and_queue(packet, is_retransmission=True) if self.write_stream_open or \ self.control_block.has_data_to_send(): self.attempt_to_send_data() else: # Mandar FIN cuando: # * El stream de escritura está cerrado, # * El estado es ESTABLISHED/CLOSE_WAIT # (i.e., todavía no se ha enviado un FIN), y # * Todo byte saliente fue exitosamente reconocido. self.attempt_to_send_FIN() def attempt_to_send_data(self): window_closed = False while self.control_block.has_data_to_send() and not window_closed: seq_number = self.control_block.get_snd_nxt() to_send = self.control_block.extract_from_out_buffer(MSS) if not to_send: # El bloque de control no devolvió nada, lo cual es un indicio # de que la ventana está cerrada. Luego, no tenemos nada más # por hacer hasta que lleguen los próximos ACKs. window_closed = True else: packet = self.build_packet(payload=to_send, seq=seq_number) self.send_and_queue(packet) def attempt_to_send_FIN(self): state_allows_closing = self.state in [ESTABLISHED, CLOSE_WAIT] if state_allows_closing and self.rqueue.empty(): fin_packet = self.build_packet(flags=[ACKFlag, FINFlag]) # Estamos enviando un FIN, y este flag se secuencia. Incrementar el # siguiente byte a enviar. self.control_block.increment_snd_nxt() new_state = FIN_WAIT1 if self.state == ESTABLISHED else LAST_ACK self.set_state(new_state) self.send_and_queue(fin_packet) def handle_incoming(self, packet): self.packet_handler.handle(packet) self.packet_sender.notify() def shutdown(self, how): if how == SHUT_RD: self.shutdown_read_stream() elif how == SHUT_WR: self.shutdown_write_stream() else: self.shutdown_read_stream() self.shutdown_write_stream() def shutdown_read_stream(self): self.read_stream_open = False def shutdown_write_stream(self): self.write_stream_open = False self.packet_sender.notify() def close(self, mode=NO_WAIT): #print 'lista rto: ' + str(self.rto_estimator.rtoList) self.close_mode = mode if self.state != CLOSED: self.shutdown(SHUT_RDWR) self.close_event.wait() self.free() self.join_threads() def free(self): self.printToFileRetransmission() #print str(self.rto_estimator.rtoList) if self.control_block is not None: self.control_block.flush_buffers() self.stop_threads() # En caso de que el establecimiento de conexión haya fallado, esto # destrabará al thread principal de la aplicación. self.connected_event.set() # Y, análogamente, esto destrabará al thread principal si se llama a # close y free es luego invocada por algún otro thread. self.close_event.set() self.set_state(CLOSED) def printToFileRetransmission(self): with open(self.filepath, 'a') as f: f.write( str(self.alpha) + ' ' + str(self.beta) + ' ' + str(self.retransmission_counter)) f.write('\n') def printToFilePacketRTT(self): with open(self.filepath, 'a') as f: for (acknumber, rtt) in self.rto_estimator.rttList: print 'acknumber: ' + str(acknumber) print 'rtt: ' + str(rtt) f.write(str(acknumber) + ' ' + str(rtt * CLOCK_TICK)) f.write('\n')
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() #Comente esto porque quedaba corrida la ventana de recepcion despues del connect #if payload is not None: # self.control_block.increment_send_seq() self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) def sendFile(self, nombre): if not self.is_connected(): raise Exception('cannot send data: connection not established') archivo = open(nombre) line = archivo.readline() self.worker.send(line) archivo.close() def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.set_destination_address(address) self.control_block.set_destination_port(port) syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # (1) Obtener los paquetes en self.retranmission_queue # (2) Volver a enviarlos # (3) Reencolarlos para otra eventual retransmisión # ...y verificar que no se exceda la cantidad máxima de reenvíos! # (hacer self.shutdown() si esto ocurre y dejar un mensaje en self.error) retransmited = RetransmissionQueue(self) shutdown = False for packet in self.retransmission_queue: seqNum = packet.get_seq_number() retransmitions = 0 if(self.retransmission_attempts.has_key(seqNum)): retransmitions = self.retransmission_attempts[seqNum] if(retransmitions == MAX_RETRANSMISSION_ATTEMPTS): shutdown = True break else: self.send_packet(packet) retransmited.put(packet) self.retransmission_attempts[seqNum] = retransmitions + 1 if(shutdown): self.shutdown() else: self.retransmission_queue = retransmited def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # * Distinguir el caso donde el estado es SYN_SENT # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # * Ajustar la ventana deslizante con #ACK # * Tener en cuenta también el caso donde el estado es FIN_SENT if not ACKFlag in packet: print("NO ACK") return print("ACK "+str(packet.get_ack_number())) if not self.control_block.ack_valid(packet.get_ack_number()): print("NOT VALID ACK "+str(packet.get_ack_number())) return print("VALID ACK "+str(packet.get_ack_number())) self.retransmission_queue.acknowledge(packet) self.control_block.adjust_window(packet.get_ack_number()) if(self.state == SYN_SENT): print("ESTABLISH CONNECTION") self.state = ESTABLISHED self.connected_event.set() return if(self.state == FIN_SENT): print("CLOSE CONNECTION") self.state = CLOSED self.close() def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = Counter() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() if payload is not None: self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): if DEBUG and RANDOM_LOSS: #'perdemos' un 10% de los paquetes if random.choice(range(10)) : print 'mando el paquete:', packet.get_seq_number() self.socket.send(packet) else: print 'Mala leche con el', packet.get_seq_number() else: self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.reset_numbers() self.control_block.set_destination_address(address) self.control_block.set_destination_port(port) syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # (1) Obtener los paquetes en self.retranmission_queue # (2) Volver a enviarlos # (3) Reencolarlos para otra eventual retransmisión # ...y verificar que no se exceda la cantidad máxima de reenvíos! # (hacer self.shutdown() si esto ocurre y dejar un mensaje en self.error) # *** NO SE SI NO HABIA YA OTRA VARIABLE DONDE SE INDICABA LA CANTIDAD DE TRANSMISIONES # *** SE CUENTA POR PAQUETE O LA COLA ENTERA? # *** HAY QUE RETRANSMITIR TODO LO QUE ESTA EN LA COLA? # Cantidad maxima de reenvios: MAX_RETRANSMISSION_ATTEMPTS if DEBUG: print 'TIMEOUT' oldQueue = self.retransmission_queue self.retransmission_queue = RetransmissionQueue(self) for packet in oldQueue: if self.retransmission_attempts[packet.get_seq_number()] == MAX_RETRANSMISSION_ATTEMPTS : self.error = 'Retransmition Limit Exceeded' self.shutdown() else: self.retransmission_attempts[packet.get_seq_number()] += 1 self.send_and_queue_packet(packet) def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): ################### ## Completar! ## ################### # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # * Distinguir el caso donde el estado es SYN_SENT # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # * Ajustar la ventana deslizante con #ACK # * Tener en cuenta también el caso donde el estado es FIN_SENT # Corroboro que el flag de ACK este seteado if not ACKFlag in packet: raise Exception('Error (hay que hacer algo mas? Es un error?)') self.shutdown() return if DEBUG: print 'me llegó el paquete numero:', packet.get_ack_number() # Reviso si el ACK es aceptado if self.control_block.ack_accepted(packet): # *** Si no es aceptado no hago nada no? for ackedPacket in self.retransmission_queue.acknowledge(packet): del(self.retransmission_attempts[ackedPacket.get_seq_number()]) self.control_block.adjust_window(packet) else: return # Si fue aceptado, ejecuto accion en base al estado actual # *** ASUMO QUE NO SE LO LLAMA SI EL ESTADO ES CLOSED, ESO ESTARA BIEN? if self.state == ESTABLISHED: return elif self.state == SYN_SENT: # El servidor establecio una conexion: # cambio el estado del cliente y sincronizo la conexion # Corroboro que el numero de ACK recibido sea igual al numero de SEQ enviado ackNum = packet.get_ack_number() seqNum = self.control_block.get_send_seq() if not (self.control_block.modular_increment(ackNum) == seqNum): #self.error = 'SYN_ACK inválido' #self.shutdown() if DEBUG: print 'recibí fruta (ack != seq):', ackNum, seqNum #no hagamos que se cierre, tal vez le llegó algo viejo o equivocado #dejemos que si posta anda mal, lo maten los timeouts. return self.state = ESTABLISHED self.connected_event.set() if DEBUG: print 'conexión establecida' elif self.state == FIN_SENT: # Recibi respuesta del servidor indicando que puedo cerrar la conexion if DEBUG: print 'llego el fin' self.state = CLOSED # *** Hago algo mas para cerrar la conexion? self.shutdown() #(?) else: self.error = 'Estado inexistente' self.shutdown() def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() #if payload is not None: self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): if (debug): print("Voy a mandar el paquete número: "), print(str(packet.get_seq_number())) #if random.randint(1, 11) == 1: # simulo congestión # return self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) global t1 t1 = time.time() def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.set_destination_address(address) self.control_block.set_destination_port(port) syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): global count_rets count_rets += 1 if (debug or True): print("Parece que se perdió un paquete, voy a reenviar.") new_queue = RetransmissionQueue(self) for packet in self.retransmission_queue: if packet not in self.retransmission_attempts: self.retransmission_attempts[packet.get_seq_number()] = 0 self.retransmission_attempts[packet.get_seq_number()] += 1 if self.retransmission_attempts[ packet.get_seq_number()] >= MAX_RETRANSMISSION_ATTEMPTS: self.shutdown() self.error = "Intentos de retransmisión superó el máximo" break else: self.send_packet(packet) new_queue.put(packet) self.retransmission_queue = new_queue def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): if self.state == ESTABLISHED and self.control_block.accept_ack(packet): self.retransmission_queue.acknowledge(packet) self.clear_retransmission_attempts(packet.get_ack_number()) if (debug): print("Recibí el ack número: "), print(str(packet.get_ack_number())) if self.outgoing_buffer.empty( ) and self.retransmission_queue.empty(): print("Recibi el ultimo ack") print time.time() - t1 global f, frets, count_rets f.write(str(time.time() - t1)) f.write("\n") f.close() frets.write(str(count_rets)) frets.write("\n") frets.close() elif self.state == SYN_SENT and self.control_block.accept_control_ack( packet): self.state = ESTABLISHED self.connected_event.set() elif self.state == FIN_SENT and self.control_block.accept_control_ack( packet): self.state = CLOSED # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # Se encarga el control_block # * Distinguir el caso donde el estado es SYN_SENT # Dale # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # A full # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # Sí # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # Lo hace la cola # * Ajustar la ventana deslizante con #ACK # Lo hace el control_block # * Tener en cuenta también el caso donde el estado es FIN_SENT # Ok def clear_retransmission_attempts(self, ack): for seq_number in self.retransmission_attempts.keys(): if seq_number <= ack: del self.retransmission_attempts[seq_number] def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
class PTCProtocol(object): def __init__(self, ack_delay=0, ack_loss_probability=0): # Modificación self.ack_delay = ack_delay self.ack_loss_probability = ack_loss_probability self.retransmissions = 0 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s:%(levelname)s:%(name)s:%(message)s', filename='ptc.log') self.logger = logging.getLogger('PTCProtocol') self.state = CLOSED self.control_block = None self.packet_builder = PacketBuilder() self.socket = Soquete(protocol=self) self.rcv_wnd = RECEIVE_BUFFER_SIZE self.iss = self.compute_iss() self.rqueue = RetransmissionQueue() self.retransmission_attempts = dict() self.read_stream_open = True self.write_stream_open = True self.packet_handler = IncomingPacketHandler(self) self.close_event = threading.Event() self.initialize_threads() def initialize_threads(self): self.packet_sender = PacketSender(self) self.packet_receiver = PacketReceiver(self) self.clock = Clock(self) self.keepalive_sender = KeepAliveSender(self) def start_threads(self): self.packet_receiver.start() self.packet_sender.start() self.clock.start() self.keepalive_sender.start() def stop_threads(self): self.packet_receiver.stop() self.packet_sender.stop() self.packet_sender.notify() self.clock.stop() self.keepalive_sender.stop() def join_threads(self): self.packet_receiver.join() self.packet_sender.join() self.clock.join() self.keepalive_sender.join() def set_state(self, state): self.state = state if state == CLOSED or state == FIN_WAIT2: self.close_event.set() if state == ESTABLISHED: self.connected_event.set() def compute_iss(self): value = random.randint(0, MAX_SEQ) return SequenceNumber(value) def initialize_control_block_from(self, packet): # +1 dado que el SYN se secuencia. receive_seq = 1 + packet.get_seq_number() send_seq = 1 + self.iss send_window = packet.get_window_size() receive_window = self.rcv_wnd self.control_block = PTCControlBlock(send_seq, receive_seq, send_window, receive_window) def is_connected(self): connected_states = [ ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK ] return self.state in connected_states def build_packet(self, seq=None, ack=None, payload=None, flags=None, window=None): if seq is None: seq = self.control_block.get_snd_nxt() if flags is None: flags = [ACKFlag] if ack is None and ACKFlag in flags: ack = self.control_block.get_rcv_nxt() if window is None: window = self.control_block.get_rcv_wnd() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq, ack=ack, window=window) return packet def send_and_queue(self, packet): self.rqueue.put(packet) self.socket.send(packet) def set_destination_on_packet_builder(self, address, port): self.packet_builder.set_destination_address(address) self.packet_builder.set_destination_port(port) def bind(self, address, port): self.socket.bind(address, port) self.packet_builder.set_source_address(address) self.packet_builder.set_source_port(port) def listen(self): self.set_state(LISTEN) def connect_to(self, address, port): self.connected_event = threading.Event() self.set_destination_on_packet_builder(address, port) self.start_threads() syn_packet = self.build_packet(seq=self.iss, flags=[SYNFlag], window=self.rcv_wnd) self.set_state(SYN_SENT) self.send_and_queue(syn_packet) self.connected_event.wait() def accept(self): if self.state != LISTEN: raise PTCError('should listen first') self.connected_event = threading.Event() self.start_threads() # Esperar hasta que un cliente desee conectarse. self.connected_event.wait() def send(self, data): with self.control_block: if not self.write_stream_open: raise PTCError('write stream is closed') self.control_block.to_out_buffer(data) self.packet_sender.notify() def receive(self, size): data = self.control_block.from_in_buffer(size) updated_rcv_wnd = self.control_block.get_rcv_wnd() if updated_rcv_wnd > 0: wnd_packet = self.build_packet(window=updated_rcv_wnd) self.logger.debug('Enviando window: window=%d' % updated_rcv_wnd) self.socket.send(wnd_packet) return data def tick(self): with self.rqueue: self.rqueue.tick() self.retransmit_packets_if_needed() def send_keepalive(self): rcv_wnd = self.control_block.get_rcv_wnd() packet = self.build_packet(window=rcv_wnd) self.logger.debug('Enviando keepalive: window=%d' % rcv_wnd) self.socket.send(packet) def retransmit_packets_if_needed(self): to_retransmit = self.rqueue.get_packets_to_retransmit() for packet in to_retransmit: attempts = self.update_retransmission_attempts_for(packet) self.logger.debug('Retransmitiendo: intento %d' % attempts) if attempts > MAX_RETRANSMISSION_ATTEMPTS: self.logger.debug('Límite de retransmisiones alcanzado') # Nos damos por vencidos. Se superó el máximo número de # retransmisiones para este paquete. self.free() else: self.retransmissions += 1 self.send_and_queue(packet) def update_retransmission_attempts_for(self, packet): seq_number = packet.get_seq_number() attempts = 1 + self.retransmission_attempts.setdefault(seq_number, 0) self.retransmission_attempts[seq_number] = attempts return attempts def acknowledge_packets_on_retransmission_queue_with(self, packet): ack_number = packet.get_ack_number() if self.control_block.ack_is_accepted(ack_number): # Sólo ACKs mayores que SND_UNA y menores que SND_NXT son válidos # en este punto. with self.rqueue: snd_una = self.control_block.get_snd_una() snd_nxt = self.control_block.get_snd_nxt() # Ver qué paquetes encolados son totalmente reconocidos por # este paquete. Necesitamos SND_UNA y SND_NXT para poder # comparar correctamente los SEQs y ACKs. removed_packets = self.rqueue.remove_acknowledged_by( packet, snd_una, snd_nxt) for removed_packet in removed_packets: seq_number = removed_packet.get_seq_number() if seq_number in self.retransmission_attempts: del self.retransmission_attempts[seq_number] def handle_outgoing(self): if self.control_block is None: # Cuando la conexión todavía no fue establecida, no tenemos nada # para enviar. return with self.control_block: if self.write_stream_open or self.control_block.has_data_to_send(): self.attempt_to_send_data() else: # Mandar FIN cuando: # * El stream de escritura está cerrado, # * El estado es ESTABLISHED/CLOSE_WAIT # (i.e., todavía no se ha enviado un FIN), y # * Todo byte saliente fue exitosamente reconocido. self.attempt_to_send_FIN() def attempt_to_send_data(self): window_closed = False while self.control_block.has_data_to_send() and not window_closed: seq_number = self.control_block.get_snd_nxt() to_send = self.control_block.extract_from_out_buffer(MSS) if not to_send: # El bloque de control no devolvió nada, lo cual es un indicio # de que la ventana está cerrada. Luego, no tenemos nada más # por hacer hasta que lleguen los próximos ACKs. window_closed = True else: packet = self.build_packet(payload=to_send, seq=seq_number) self.send_and_queue(packet) def attempt_to_send_FIN(self): state_allows_closing = self.state in [ESTABLISHED, CLOSE_WAIT] if state_allows_closing and self.rqueue.empty(): fin_packet = self.build_packet(flags=[ACKFlag, FINFlag]) # Estamos enviando un FIN, y este flag se secuencia. Incrementar el # siguiente byte a enviar. self.control_block.increment_snd_nxt() new_state = FIN_WAIT1 if self.state == ESTABLISHED else LAST_ACK self.set_state(new_state) self.send_and_queue(fin_packet) def handle_incoming(self, packet): self.packet_handler.handle(packet) self.packet_sender.notify() def shutdown(self, how): if how == SHUT_RD: self.shutdown_read_stream() elif how == SHUT_WR: self.shutdown_write_stream() else: self.shutdown_read_stream() self.shutdown_write_stream() def shutdown_read_stream(self): self.read_stream_open = False def shutdown_write_stream(self): self.write_stream_open = False self.packet_sender.notify() def close(self): if self.state != CLOSED: self.shutdown(SHUT_RDWR) self.close_event.wait() self.free() self.join_threads() def free(self): if self.control_block is not None: self.control_block.flush_buffers() self.stop_threads() # En caso de que el establecimiento de conexión haya fallado, esto # destrabará al thread principal de la aplicación. self.connected_event.set() # Y, análogamente, esto destrabará al thread principal si se llama a # close y free es luego invocada por algún otro thread. self.close_event.set() self.set_state(CLOSED)
class PTCClientProtocol(object): def __init__(self, address, port): self.retransmission_queue = RetransmissionQueue(self) self.retransmission_attempts = dict() self.outgoing_buffer = DataBuffer() self.state = CLOSED self.control_block = ClientControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) def is_connected(self): return self.state == ESTABLISHED def build_packet(self, payload=None, flags=None): seq = self.control_block.get_send_seq() #if payload is not None: self.control_block.increment_send_seq() packet = self.packet_builder.build(payload=payload, flags=flags, seq=seq) return packet def send_packet(self, packet): if(debug): print("Voy a mandar el paquete número: "), print(str(packet.get_seq_number())) #if random.randint(1, 11) == 1: # simulo congestión # return self.socket.send(packet) def send_and_queue_packet(self, packet): self.send_packet(packet) self.retransmission_queue.put(packet) def send(self, data): if not self.is_connected(): raise Exception('cannot send data: connection not established') self.worker.send(data) global t1 t1 = time.time() def connect_to(self, address, port): self.worker = ClientProtocolWorker.spawn_for(self) self.worker.start() self.connected_event = threading.Event() self.control_block.set_destination_address(address) self.control_block.set_destination_port(port) syn_packet = self.build_packet(flags=[SYNFlag]) self.send_and_queue_packet(syn_packet) self.state = SYN_SENT self.connected_event.wait() def handle_timeout(self): global count_rets count_rets += 1 if(debug or True): print ("Parece que se perdió un paquete, voy a reenviar.") new_queue = RetransmissionQueue(self) for packet in self.retransmission_queue: if packet not in self.retransmission_attempts: self.retransmission_attempts[packet.get_seq_number()] = 0 self.retransmission_attempts[packet.get_seq_number()] += 1 if self.retransmission_attempts[packet.get_seq_number()] >= MAX_RETRANSMISSION_ATTEMPTS: self.shutdown() self.error = "Intentos de retransmisión superó el máximo" break else: self.send_packet(packet) new_queue.put(packet) self.retransmission_queue = new_queue def handle_pending_data(self): more_data_pending = False if self.control_block.send_allowed(): try: data = self.outgoing_buffer.get(MIN_PACKET_SIZE, MAX_PACKET_SIZE) except NotEnoughDataException: pass else: packet = self.build_packet(payload=data) self.send_and_queue_packet(packet) if not self.outgoing_buffer.empty(): more_data_pending = True else: more_data_pending = True if more_data_pending: self.worker.signal_pending_data() def handle_incoming(self, packet): if self.state == ESTABLISHED and self.control_block.accept_ack(packet): self.retransmission_queue.acknowledge(packet) self.clear_retransmission_attempts(packet.get_ack_number()) if(debug): print("Recibí el ack número: "), print(str(packet.get_ack_number())) if self.outgoing_buffer.empty() and self.retransmission_queue.empty(): print("Recibi el ultimo ack") print time.time() - t1 global f, frets, count_rets f.write(str(time.time() - t1)) f.write("\n") f.close() frets.write(str(count_rets)) frets.write("\n") frets.close() elif self.state == SYN_SENT and self.control_block.accept_control_ack(packet): self.state = ESTABLISHED self.connected_event.set() elif self.state == FIN_SENT and self.control_block.accept_control_ack(packet): self.state = CLOSED # Tener en cuenta que se debe: # * Corroborar que el flag de ACK esté seteado # Se encarga el control_block # * Distinguir el caso donde el estado es SYN_SENT # Dale # * No olvidar de hacer self.connected_event.set() al confirmar el ACK y establecer la conexión!!! # A full # * Analizar si #ACK es aceptado (hablar con el bloque de control para hacer este checkeo) # Sí # * Sacar de la cola de retransmisión los paquetes reconocidos por #ACK # Lo hace la cola # * Ajustar la ventana deslizante con #ACK # Lo hace el control_block # * Tener en cuenta también el caso donde el estado es FIN_SENT # Ok def clear_retransmission_attempts(self, ack): for seq_number in self.retransmission_attempts.keys(): if seq_number <= ack: del self.retransmission_attempts[seq_number] def handle_close_connection(self): if not self.outgoing_buffer.empty(): self.worker.signal_pending_data() self.worker.signal_close_connection() elif not self.retransmission_queue.empty(): self.worker.signal_close_connection() else: fin_packet = self.build_packet(flags=[FINFlag]) self.send_and_queue_packet(fin_packet) self.state = FIN_SENT def close(self): if self.is_connected(): self.worker.signal_close_connection() def shutdown(self): self.outgoing_buffer.clear() self.retransmission_queue.clear() self.retransmission_attempts.clear() self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() self.state = CLOSED
class PTCServerProtocol(object): def __init__(self, address, port): self.state = CLOSED self.control_block = ServerControlBlock(address, port) self.socket = Soquete(address, port) self.packet_builder = PacketBuilder(self) self.connection_closed_event = threading.Event() def is_connected(self): return self.state == ESTABLISHED def is_closed(self): return self.state == CLOSED def build_packet(self, flags=None): ack = self.control_block.get_receive_seq() packet = self.packet_builder.build(flags=flags, ack=ack) return packet def send_packet(self, packet): self.socket.send(packet) def receive(self, size): if self.is_closed() and self.incoming_buffer.empty(): raise Exception('cannot receive: connection ended and no more pending data buffered') elif self.incoming_buffer.empty() and not self.is_connected(): raise Exception('cannot receive: connection not established') if size < MIN_PACKET_SIZE: size = MIN_PACKET_SIZE data = self.incoming_buffer.sync_get(MIN_PACKET_SIZE, size) return data def accept(self): self.incoming_buffer = DataBuffer() self.state = CLOSED self.worker = ServerProtocolWorker.spawn_for(self) self.worker.start() # No hay mucho por hacer... simplemente esperar a que caiga el SYN del cliente self.connected_event = threading.Event() self.connected_event.wait() def handle_incoming(self, packet): seq_number = packet.get_seq_number() if(debug): print ("Recibi el paquete numero: "), print (seq_number) if SYNFlag in packet: self.control_block.set_destination_address(packet.get_source_ip()) self.control_block.set_destination_port(packet.get_source_port()) self.control_block.set_receive_seq(seq_number) ack_packet = self.build_packet(flags=[ACKFlag]) self.send_packet(ack_packet) self.control_block.set_receive_seq(seq_number) self.control_block.increment_receive_seq() self.state = ESTABLISHED self.connected_event.set() if FINFlag in packet: if self.control_block.accepts(seq_number): self.state = FIN_RECEIVED self.send_ack() self.close() else: data = packet.get_payload() if self.control_block.accepts(seq_number) and data: self.incoming_buffer.put(data) if self.state != FIN_RECEIVED: self.send_ack() self.control_block.increment_receive_seq() def send_ack(self): ack_packet = self.build_packet(flags=[ACKFlag]) self.send_packet(ack_packet) def shutdown(self): self.worker.stop() # Esto es por si falló el establecimiento de conexión (para destrabar al thread principal) self.connected_event.set() def close(self): self.state = CLOSED self.connection_closed_event.set() def wait_for_close(self): self.connection_closed_event.wait()