Example #1
0
    def send_multicast_beacons(self,
                               interfaces,
                               beacon_type='solicitation',
                               verbose=False):
        """Sends out multicast beacons on each interface in `interfaces`.

        :param interfaces: The output of `get_all_interfaces_definition()`.
        :param beacon_type: Type of beacon to send. (Default: 'solicitation'.)
        :param verbose: If True, will log the payload of each beacon sent.
        """
        for ifname, ifdata in interfaces.items():
            log.msg("Sending multicast beacon on '%s'." % ifname)
            if not ifdata['enabled']:
                continue
            remote = interface_info_to_beacon_remote_payload(ifname, ifdata)
            # We'll make slight adjustments to the beacon payload depending
            # on the configured source subnet (if any), but the basic payload
            # is ready.
            payload = {"remote": remote}
            links = ifdata["links"]
            if len(links) == 0:
                # No configured links, so try sending out a link-local IPv6
                # multicast beacon.
                beacon = create_beacon_payload(beacon_type, payload)
                if verbose:
                    log.msg("Beacon payload:\n%s" % pformat(beacon.payload))
                self.send_multicast_beacon(ifdata["index"], beacon)
                continue
            sent_ipv6 = False
            for link in ifdata["links"]:
                subnet = link["address"]
                remote['subnet'] = subnet
                address = subnet.split("/")[0]
                beacon = create_beacon_payload(beacon_type, payload)
                if verbose:
                    log.msg("Beacon payload:\n%s" % pformat(beacon.payload))
                if ':' not in address:
                    # An IPv4 socket requires the source address to be the
                    # IPv4 address assigned to the interface.
                    self.send_multicast_beacon(address, beacon)
                else:
                    # An IPv6 socket requires the source address to be the
                    # interface index.
                    self.send_multicast_beacon(ifdata["index"], beacon)
                    sent_ipv6 = True
            if not sent_ipv6:
                remote['subnet'] = None
                beacon = create_beacon_payload(beacon_type, payload)
                self.send_multicast_beacon(ifdata["index"], beacon)
Example #2
0
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
Example #3
0
 def test__succeeds_when_shared_secret_present(self):
     self.write_secret()
     beacon = create_beacon_payload("solicitation", payload={})
     self.assertThat(beacon.type, Equals("solicitation"))
     self.assertThat(
         beacon.payload["type"], Equals(BEACON_TYPES["solicitation"])
     )
Example #4
0
 def test__creates_packet_that_can_decode(self):
     self.write_secret()
     random_type = random.choice(list(BEACON_TYPES.keys()))
     random_key = factory.make_string(prefix="_")
     random_value = factory.make_string()
     packet_bytes, _, _, _ = create_beacon_payload(
         random_type, payload={random_key: random_value})
     decrypted = read_beacon_payload(packet_bytes)
     self.assertThat(decrypted.type, Equals(random_type))
     self.assertThat(decrypted.payload[random_key], Equals(random_value))
Example #5
0
 def test__supplements_data_and_returns_complete_data(self):
     self.write_secret()
     random_type = random.choice(list(BEACON_TYPES.keys()))
     random_key = factory.make_string(prefix="_")
     random_value = factory.make_string()
     beacon = create_beacon_payload(random_type,
                                    payload={random_key: random_value})
     # Ensure a valid UUID was added.
     self.assertIsNotNone(UUID(beacon.payload['uuid']))
     self.assertThat(beacon.type, Equals(random_type))
     # The type is replicated here for authentication purposes.
     self.assertThat(beacon.payload['type'],
                     Equals(BEACON_TYPES[random_type]))
     self.assertThat(beacon.payload[random_key], Equals(random_value))
Example #6
0
 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()
Example #7
0
 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:..."),
     )
Example #8
0
    def beaconReceived(self, beacon_json):
        """Called whenever a beacon is received.

        This method is responsible for updating the `tx_queue` and `rx_queue`
        data structures, and determining if the incoming beacon is meaningful
        for determining network topology.

        :param beacon_json: The normalized beacon JSON, which can come either
            from the external tcpdump-based process, or from the sockets layer
            (with less information about the received packet).
        """
        rx_uuid = beacon_json.get('payload', {}).get("uuid")
        if rx_uuid is None:
            if self.debug is True:
                log.msg("Rejecting incoming beacon: no UUID found: \n%s" %
                        (pformat(beacon_json)))
            return
        own_beacon = False
        if self.tx_queue.get(rx_uuid):
            own_beacon = True
        is_dup = self.remember_beacon_and_check_duplicate(rx_uuid, beacon_json)
        if self.debug is True:
            log.msg("%s %sreceived:\n%s" %
                    ("Own beacon" if own_beacon else "Beacon",
                     "(duplicate) " if is_dup else "", beacon_json))
        # From what we know so far, we can infer some facts about the network.
        # (1) If we received our own beacon, that means the interface we sent
        # the packet out on is on the same fabric as the interface that
        # received it.
        # (2) If we receive a duplicate beacon on two different interfaces,
        # that means those two interfaces are on the same fabric.
        reply_ip = beacon_json['source_ip']
        reply_port = beacon_json['source_port']
        if ':' not in reply_ip:
            # Since we opened an IPv6-compatible socket, need IPv6 syntax
            # here to send to IPv4 addresses.
            reply_ip = '::ffff:' + reply_ip
        reply_address = (reply_ip, reply_port)
        beacon_type = beacon_json['type']
        if beacon_type == "solicitation":
            receive_interface_info = self.get_receive_interface_info(
                beacon_json)
            payload = {"interface": receive_interface_info, "acks": rx_uuid}
            reply = create_beacon_payload("advertisement", payload)
            self.send_beacon(reply, reply_address)
Example #9
0
 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()
Example #10
0
 def test__returns_beaconpayload_namedtuple(self):
     beacon = create_beacon_payload("solicitation")
     self.assertThat(beacon.bytes, IsInstance(bytes))
     self.assertThat(beacon.payload, Is(None))
     self.assertThat(beacon.type, Equals("solicitation"))
     self.assertThat(beacon.version, Equals(1))
Example #11
0
 def test__requires_maas_shared_secret_for_inner_data_payload(self):
     with ExpectedException(MissingSharedSecret,
                            ".*shared secret not found.*"):
         create_beacon_payload("solicitation", payload={})
Example #12
0
 def test__is_valid__succeeds_for_valid_payload(self):
     beacon = create_beacon_payload("solicitation")
     beacon_packet = BeaconingPacket(beacon.bytes)
     self.assertTrue(beacon_packet.valid)