def test__creates_listen_port_when_run_with_IReactorMulticast(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol(reactor, port=0) self.assertThat(protocol.listen_port, Not(Is(None))) # This tests that the post gets closed properly; otherwise the test # suite will complain about things left in the reactor. yield protocol.stopProtocol()
def test__skips_creating_listen_port_when_run_with_fake_reactor(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol(Clock(), port=0) self.assertThat(protocol.listen_port, Is(None)) # No listen port, so stopProtocol() shouldn't return a Deferred. result = protocol.stopProtocol() self.assertThat(result, Is(None))
def test_send_multicast_beacons(self): interfaces = { "eth0": { "enabled": True, "links": [] }, "eth1": { "enabled": True, "links": [] }, "eth2": { "enabled": True, "links": [] }, } self.patch(socket, "if_nametoindex", lambda name: int(name[3:])) protocol = BeaconingSocketProtocol( Clock(), port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) self.patch(services, "create_beacon_payload") send_mcast_mock = self.patch(protocol, "send_multicast_beacon") protocol.send_multicast_beacons(interfaces) # beaconing is sent for each interface ID self.assertEqual( send_mcast_mock.mock_calls, [call(0, ANY), call(1, ANY), call(2, ANY)], )
def test__hints_for_same_beacon_seen_on_multiple_interfaces(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol( reactor, port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) # Don't try to send out any replies. self.patch(services, "create_beacon_payload") self.patch(protocol, "send_beacon") # Need to generate a real UUID with the current time, so it doesn't # get aged out. uuid = str(uuid1()) # Make the protocol think we sent a beacon with this UUID already. fake_tx_beacon = FakeBeaconPayload(uuid, ifname="eth0") fake_rx_beacon_eth0 = { "source_ip": "127.0.0.1", "source_port": 5240, "destination_ip": "224.0.0.118", "interface": "eth0", "type": "solicitation", "payload": fake_tx_beacon.payload, } fake_rx_beacon_eth1 = { "source_ip": "127.0.0.1", "source_port": 5240, "destination_ip": "224.0.0.118", "interface": "eth1", "vid": 100, "type": "solicitation", "payload": fake_tx_beacon.payload, } protocol.beaconReceived(fake_rx_beacon_eth0) protocol.beaconReceived(fake_rx_beacon_eth1) hints = protocol.topology_hints[uuid] expected_hints = { TopologyHint( ifname="eth0", vid=None, hint="same_local_fabric_as", related_ifname="eth1", related_vid=100, related_mac=None, ), TopologyHint( ifname="eth1", vid=100, hint="same_local_fabric_as", related_ifname="eth0", related_vid=None, related_mac=None, ), } self.assertThat(hints, Equals(expected_hints)) yield protocol.stopProtocol()
def test__send_multicast_beacon_sets_ipv4_source(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol(reactor, port=0, process_incoming=True, loopback=True, interface="::", debug=False) self.assertThat(protocol.listen_port, Not(Is(None))) listen_port = protocol.listen_port._realPortNumber self.write_secret() beacon = create_beacon_payload("advertisement", {}) protocol.send_multicast_beacon("127.0.0.1", beacon, port=listen_port) # Verify that we received the packet. yield wait_for_rx_packets(protocol, 1) yield protocol.stopProtocol()
def test__sends_and_receives_unicast_beacons(self): # Note: Always use a random port for testing. (port=0) logger = self.useFixture(TwistedLoggerFixture()) protocol = BeaconingSocketProtocol( reactor, port=0, process_incoming=True, loopback=True, interface="::", debug=True, ) self.assertThat(protocol.listen_port, Not(Is(None))) listen_port = protocol.listen_port._realPortNumber self.write_secret() beacon = create_beacon_payload("solicitation", {}) rx_uuid = beacon.payload["uuid"] destination = random.choice(["::ffff:127.0.0.1", "::1"]) protocol.send_beacon(beacon, (destination, listen_port)) # Pretend we didn't send this packet. Otherwise we won't reply to it. # We have to do this now, before the reactor runs again. transmitted = protocol.tx_queue.pop(rx_uuid, None) # Since we've instructed the protocol to loop back packets for testing, # it should have sent a multicast solicitation, received it back, sent # an advertisement, then received it back. So we'll wait for two # packets to be sent. yield wait_for_rx_packets(protocol, 2) # Grab the beacon we know we transmitted and then received. received = protocol.rx_queue.pop(rx_uuid, None) self.assertThat(transmitted, Equals(beacon)) self.assertThat(received[0].json["payload"]["uuid"], Equals(rx_uuid)) # Grab the subsequent packets from the queues. transmitted = protocol.tx_queue.popitem()[1] received = protocol.rx_queue.popitem()[1] # We should have received a second packet to ack the first beacon. self.assertThat(received[0].json["payload"]["acks"], Equals(rx_uuid)) # We should have transmitted an advertisement in response to the # solicitation. self.assertThat(transmitted.type, Equals("advertisement")) # This tests that the post gets closed properly; otherwise the test # suite will complain about things left in the reactor. yield protocol.stopProtocol() # In debug mode, the logger should have printed each packet. self.assertThat( logger.output, DocTestMatches("...Beacon received:...Own beacon received:..."), )
def test__queues_multicast_beacon_soliciations_upon_request(self): # Note: Always use a random port for testing. (port=0) clock = Clock() protocol = BeaconingSocketProtocol( clock, port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) # Don't try to send out any replies. self.patch(services, "create_beacon_payload") send_mcast_mock = self.patch(protocol, "send_multicast_beacons") self.patch(protocol, "send_beacon") yield protocol.queueMulticastBeaconing(solicitation=True) clock.advance(0) self.assertThat(send_mcast_mock, MockCalledOnceWith({}, "solicitation"))
def do_beaconing(args, interfaces=None): """Sends out beacons based on the given arguments, and waits for replies. :param args: The command-line arguments. :param interfaces: The interfaces to send out beacons on. Must be the result of `get_all_interfaces_definition()`. """ if args.source is None: source_ip = '::' else: source_ip = args.source protocol = BeaconingSocketProtocol(reactor, process_incoming=True, debug=True, interface=source_ip, port=args.port, interfaces=interfaces) if args.destination is None: destination_ip = "::ffff:" + BEACON_IPV4_MULTICAST elif ':' not in args.destination: destination_ip = "::ffff:" + args.destination else: destination_ip = args.destination if "224.0.0.118" in destination_ip: protocol.send_multicast_beacons(interfaces, verbose=args.verbose) else: log.msg("Sending unicast beacon to '%s'..." % destination_ip) beacon = create_beacon_payload("solicitation") protocol.send_beacon(beacon, (destination_ip, BEACON_PORT)) reactor.callLater(args.timeout, lambda: reactor.stop()) reactor.run() return protocol
def test__hints_for_own_beacon_received_on_same_interface(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol( reactor, port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) # Need to generate a real UUID with the current time, so it doesn't # get aged out. uuid = str(uuid1()) # Make the protocol think we sent a beacon with this UUID already. fake_tx_beacon = FakeBeaconPayload(uuid, ifname="eth0") protocol.tx_queue[uuid] = fake_tx_beacon fake_rx_beacon = { "source_ip": "127.0.0.1", "source_port": 5240, "destination_ip": "224.0.0.118", "interface": "eth0", "type": "solicitation", "payload": fake_tx_beacon.payload, } protocol.beaconReceived(fake_rx_beacon) # Should only have created one hint. hint = protocol.topology_hints[uuid].pop() self.assertThat(hint.hint, Equals("rx_own_beacon_on_tx_interface")) yield protocol.stopProtocol()
def test__multicasts_at_most_once_per_five_seconds(self): # Note: Always use a random port for testing. (port=0) clock = Clock() protocol = BeaconingSocketProtocol( clock, port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) # Don't try to send out any replies. self.patch(services, "create_beacon_payload") monotonic_mock = self.patch(services.time, "monotonic") send_mcast_mock = self.patch(protocol, "send_multicast_beacons") self.patch(protocol, "send_beacon") monotonic_mock.side_effect = [ # Initial queue 6, # Initial dequeue 6, # Second queue (hasn't yet been 5 seconds) 10, # Third queue 11, # Second dequeue 11, ] yield protocol.queueMulticastBeaconing() clock.advance(0) self.assertThat(send_mcast_mock, MockCalledOnceWith({}, "advertisement")) send_mcast_mock.reset_mock() yield protocol.queueMulticastBeaconing() yield protocol.queueMulticastBeaconing(solicitation=True) clock.advance(4.9) self.assertThat(send_mcast_mock, MockNotCalled()) clock.advance(0.1) self.assertThat(send_mcast_mock, MockCalledOnceWith({}, "solicitation"))
def test__send_multicast_beacon_sets_ipv6_source(self): # Due to issues beyond my control, this test doesn't do what I expected # it to do. But it's still useful for code coverage (to make sure no # blatant exceptions occur in the IPv6 path). # self.skipTest( # "IPv6 loopback multicast isn't working, for whatever reason.") # Since we can't test IPv6 multicast on the loopback interface, another # method can be used to verify that it's working: # (1) sudo tcpdump -i <physical-interface> 'udp and port == 5240' # (2) bin/maas-rack send-beacons -p 5240 # Verifying IPv6 (and IPv4) multicast group join behavior can be # validated by doing something like: # (1) bin/maas-rack send-beacons -t 600 # (the high timeout will cause it to wait for 10 minutes) # (2) ip maddr show | egrep 'ff02::15a|224.0.0.118|$' # The expected result from command (2) will be that 'egrep' will # highlight the MAAS multicast groups in red text. Any Ethernet # interface with an assigned IPv4 address should have joined the # 224.0.0.118 group. All Ethernet interfaces should have joined the # 'ff02::15a' group. # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol( reactor, port=0, process_incoming=True, loopback=True, interface="::", debug=False, ) self.assertThat(protocol.listen_port, Not(Is(None))) listen_port = protocol.listen_port._realPortNumber self.write_secret() beacon = create_beacon_payload("advertisement", {}) # The loopback interface ifindex should always be 1; this is saying # to send an IPv6 multicast on ifIndex == 1. protocol.send_multicast_beacon(1, beacon, port=listen_port) # Instead of skipping the test, just don't expect to receive anything. # yield wait_for_rx_packets(protocol, 1) yield protocol.stopProtocol()
def test__getJSONTopologyHints_converts_hints_to_dictionary(self): # Note: Always use a random port for testing. (port=0) protocol = BeaconingSocketProtocol( reactor, port=0, process_incoming=False, loopback=True, interface="::", debug=True, ) # Don't try to send out any replies. self.patch(services, "create_beacon_payload") self.patch(protocol, "send_beacon") # Need to generate a real UUID with the current time, so it doesn't # get aged out. uuid = str(uuid1()) # Make the protocol think we sent a beacon with this UUID already. tx_mac = factory.make_mac_address() fake_tx_beacon = FakeBeaconPayload(uuid, ifname="eth1", mac=tx_mac, vid=100) fake_rx_beacon = { "source_ip": "127.0.0.1", "source_port": 5240, "destination_ip": "224.0.0.118", "interface": "eth0", "type": "solicitation", "payload": fake_tx_beacon.payload, } protocol.beaconReceived(fake_rx_beacon) all_hints = protocol.getJSONTopologyHints() expected_hints = [ # Note: since vid=None on the received beacon, we expect that # the hint won't have a 'vid' field. dict( ifname="eth0", hint="on_remote_network", related_ifname="eth1", related_vid=100, related_mac=tx_mac, ) ] self.assertThat(all_hints, Equals(expected_hints)) yield protocol.stopProtocol()