From 19a462aa9c5e9386dfd24fddd95999efce11a352 Mon Sep 17 00:00:00 2001 From: Sam Russell Date: Wed, 16 May 2018 21:12:34 +1200 Subject: [PATCH] Add OpenSent state to state machine Working towards #6, now we need configure an OpenDelayTimer and plug in --- beka/state_machine.py | 33 +++++++++++++++-- test/test_state_machine.py | 72 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/beka/state_machine.py b/beka/state_machine.py index 28a91d6..7197bdc 100644 --- a/beka/state_machine.py +++ b/beka/state_machine.py @@ -58,10 +58,11 @@ def shutdown(self, message): raise IdleError("State machine stopping: %s" % message) def handle_timers(self, tick): - if self.state == "open_confirm" or self.state == "established": + if self.state == "open_sent" or self.state == "open_confirm" or self.state == "established": if self.timers["hold"] + self.hold_time <= tick: self.handle_hold_timer() - elif self.timers["keepalive"] + self.keepalive_time <= tick: + if self.state == "open_confirm" or self.state == "established": + if self.timers["keepalive"] + self.keepalive_time <= tick: self.handle_keepalive_timer(tick) def handle_hold_timer(self): @@ -77,6 +78,8 @@ def handle_keepalive_timer(self, tick): def handle_message(self, message, tick):# state machine if self.state == "active": self.handle_message_active_state(message, tick) + elif self.state == "open_sent": + self.handle_message_open_sent_state(message, tick) elif self.state == "open_confirm": self.handle_message_open_confirm_state(message, tick) elif self.state == "established": @@ -110,6 +113,32 @@ def handle_message_active_state(self, message, tick): else: self.shutdown("Invalid message in Active state: %s" % str(message)) + def handle_message_open_sent_state(self, message, tick): + if isinstance(message, BgpOpenMessage): + # TODO sanity check incoming open message + if "fourbyteas" in message.capabilities: + self.fourbyteas = message.capabilities["fourbyteas"] + + if self.open_handler: + self.open_handler(message.capabilities) + + capabilities = { + "fourbyteas": [self.local_as] + } + ipv4_capabilities = {"multiprotocol": ["ipv4-unicast"]} + ipv6_capabilities = {"multiprotocol": ["ipv6-unicast"]} + if isinstance(self.local_address, IP4Address): + capabilities.update(ipv4_capabilities) + elif isinstance(self.local_address, IP6Address): + capabilities.update(ipv6_capabilities) + keepalive_message = BgpKeepaliveMessage() + self.output_messages.put(keepalive_message) + self.timers["hold"] = tick + self.timers["keepalive"] = tick + self.state = "open_confirm" + else: + self.shutdown("Invalid message in OpenSent state: %s" % str(message)) + def handle_message_open_confirm_state(self, message, tick): if isinstance(message, BgpKeepaliveMessage): for message in self.build_update_messages(): diff --git a/test/test_state_machine.py b/test/test_state_machine.py index 50dbbfd..ff79893 100644 --- a/test/test_state_machine.py +++ b/test/test_state_machine.py @@ -76,6 +76,76 @@ def test_update_message_advances_to_idle(self): self.state_machine.event(EventMessageReceived(message), self.tick) self.assertEqual(self.state_machine.state, "idle") +class StateMachineOpenSentTestCase(unittest.TestCase): + def setUp(self): + self.tick = 10000 + self.open_handler = MagicMock() + self.state_machine = StateMachine(local_as=65001, peer_as=65002, local_address="1.1.1.1", router_id="1.1.1.1", neighbor="2.2.2.2", hold_time=240, open_handler=self.open_handler) + self.old_hold_timer = self.state_machine.timers["hold"] + self.old_keepalive_timer = self.state_machine.timers["keepalive"] + self.assertEqual(self.state_machine.state, "active") + # TODO trigger this correctly + self.state_machine.state = "open_sent" + self.state_machine.timers["hold"] = self.tick + self.assertEqual(self.state_machine.output_messages.qsize(), 0) + + def test_shutdown_message_advances_to_idle(self): + with self.assertRaises(IdleError) as context: + self.state_machine.event(EventShutdown(), self.tick) + self.assertEqual(self.state_machine.state, "idle") + self.assertTrue("Shutdown requested" in str(context.exception)) + + def test_hold_timer_expired_event_advances_to_idle_and_sends_notification(self): + self.state_machine.timers["hold"] = self.tick - 3600 + with self.assertRaises(IdleError) as context: + self.state_machine.event(EventTimerExpired(), self.tick) + self.assertEqual(self.state_machine.state, "idle") + self.assertEqual(self.state_machine.output_messages.qsize(), 1) + message = self.state_machine.output_messages.get() + self.assertEqual(message.error_code, 4) # Hold Timer Expired + + def test_keepalive_timer_expired_event_does_nothing(self): + self.state_machine.timers["keepalive"] = self.tick - 3600 + self.state_machine.event(EventTimerExpired(), self.tick) + self.assertEqual(self.state_machine.state, "open_sent") + self.assertEqual(self.tick, self.state_machine.timers["hold"]) + self.assertEqual(self.state_machine.output_messages.qsize(), 0) + self.assertEqual(self.state_machine.route_updates.qsize(), 0) + + def test_open_message_advances_to_open_confirm_and_sets_timers(self): + capabilities = {"multiprotocol": "ipv4-unicast"} + message = BgpOpenMessage(4, 65002, 240, IP4Address.from_string("2.2.2.2"), capabilities) + self.state_machine.event(EventMessageReceived(message), self.tick) + self.assertEqual(self.state_machine.state, "open_confirm") + self.assertEqual(self.state_machine.output_messages.qsize(), 1) + self.open_handler.assert_called_with(capabilities) + self.assertTrue(isinstance(self.state_machine.output_messages.get(), BgpKeepaliveMessage)) + self.assertEqual(self.state_machine.timers["hold"], self.tick) + self.assertEqual(self.state_machine.timers["keepalive"], self.tick) + + def test_keepalive_message_advances_to_idle(self): + message = BgpKeepaliveMessage() + with self.assertRaises(IdleError) as context: + self.state_machine.event(EventMessageReceived(message), self.tick) + self.assertEqual(self.state_machine.state, "idle") + + def test_notification_message_advances_to_idle(self): + message = BgpNotificationMessage(0, 0, b"") + with self.assertRaises(IdleError) as context: + self.state_machine.event(EventMessageReceived(message), self.tick) + self.assertEqual(self.state_machine.state, "idle") + + def test_update_message_advances_to_idle(self): + path_attributes = { + "next_hop" : IP4Address.from_string("5.4.3.2"), + "as_path" : "65032 65011 65002", + "origin" : "EGP" + } + message = BgpUpdateMessage([], path_attributes, [IP4Prefix.from_string("192.168.0.0/16")]) + with self.assertRaises(IdleError) as context: + self.state_machine.event(EventMessageReceived(message), self.tick) + self.assertEqual(self.state_machine.state, "idle") + class StateMachineOpenConfirmTestCase(unittest.TestCase): def setUp(self): self.tick = 10000 @@ -98,7 +168,6 @@ def test_shutdown_message_advances_to_idle_and_sends_notification(self): self.assertEqual(message.error_code, 6) # Cease def test_hold_timer_expired_event_advances_to_idle_and_sends_notification(self): - self.tick = self.old_hold_timer self.state_machine.timers["hold"] = self.tick - 3600 with self.assertRaises(IdleError) as context: self.state_machine.event(EventTimerExpired(), self.tick) @@ -318,7 +387,6 @@ def test_shutdown_message_advances_to_idle_and_sends_notification(self): self.assertEqual(message.error_code, 6) # Cease def test_hold_timer_expired_event_advances_to_idle_and_sends_notification(self): - self.tick = self.old_hold_timer self.state_machine.timers["hold"] = self.tick - 3600 with self.assertRaises(IdleError) as context: self.state_machine.event(EventTimerExpired(), self.tick)