/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* nm-platform.c - Handle runtime kernel networking configuration * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2015 Red Hat, Inc. */ #include "nm-default.h" #include #include #include #include #include #include "nm-lldp-listener.h" #include "nm-sd.h" #include "sd-lldp.h" #include "test-common.h" #include "nm-test-utils.h" /*****************************************************************************/ static GVariant * get_lldp_neighbor (GVariant *neighbors, int chassis_id_type, const char *chassis_id, int port_id_type, const char *port_id) { GVariantIter iter; GVariant *variant; GVariant *result = NULL; nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}")); g_assert (chassis_id_type >= -1 && chassis_id_type <= G_MAXUINT8); g_assert (port_id_type >= -1 && port_id_type <= G_MAXUINT8); g_variant_iter_init (&iter, neighbors); while (g_variant_iter_next (&iter, "@a{sv}", &variant)) { gs_unref_variant GVariant *v_chassis_id_type = NULL; gs_unref_variant GVariant *v_chassis_id = NULL; gs_unref_variant GVariant *v_port_id_type = NULL; gs_unref_variant GVariant *v_port_id = NULL; v_chassis_id_type = g_variant_lookup_value (variant, NM_LLDP_ATTR_CHASSIS_ID_TYPE, G_VARIANT_TYPE_UINT32); g_assert (v_chassis_id_type); v_chassis_id = g_variant_lookup_value (variant, NM_LLDP_ATTR_CHASSIS_ID, G_VARIANT_TYPE_STRING); g_assert (v_chassis_id); v_port_id_type = g_variant_lookup_value (variant, NM_LLDP_ATTR_PORT_ID_TYPE, G_VARIANT_TYPE_UINT32); g_assert (v_port_id_type); v_port_id = g_variant_lookup_value (variant, NM_LLDP_ATTR_PORT_ID, G_VARIANT_TYPE_STRING); g_assert (v_port_id); if ( nm_streq (g_variant_get_string (v_chassis_id, NULL), chassis_id) && nm_streq (g_variant_get_string (v_port_id, NULL), port_id) && NM_IN_SET (chassis_id_type, -1, g_variant_get_uint32 (v_chassis_id_type)) && NM_IN_SET (port_id_type, -1, g_variant_get_uint32 (v_port_id_type))) { g_assert (!result); result = variant; } else g_variant_unref (variant); } return result; } typedef struct { int ifindex; int fd; guint8 mac[ETH_ALEN]; } TestRecvFixture; typedef struct { gsize frame_len; const uint8_t *frame; } TestRecvFrame; #define TEST_RECV_FRAME_DEFINE(name, ...) \ static const guint8 _##name##_v[] = { __VA_ARGS__ }; \ static const TestRecvFrame name = { \ .frame_len = sizeof (_##name##_v), \ .frame = _##name##_v, \ } typedef struct { guint expected_num_called; gsize frames_len; const TestRecvFrame *frames[10]; void (*check) (GMainLoop *loop, NMLldpListener *listener); } TestRecvData; #define TEST_RECV_DATA_DEFINE(name, _expected_num_called, _check, ...) \ static const TestRecvData name = { \ .expected_num_called = _expected_num_called, \ .check = _check, \ .frames_len = NM_NARG (__VA_ARGS__), \ .frames = { __VA_ARGS__ }, \ } #define TEST_IFNAME "nm-tap-test0" TEST_RECV_FRAME_DEFINE (_test_recv_data0_frame0, /* Ethernet header */ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ 0x88, 0xcc, /* Ethertype */ /* LLDP mandatory TLVs */ 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */ /* LLDP optional TLVs */ 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */ 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */ 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */ 0x00, 0x00 /* End Of LLDPDU */ ); static void _test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener) { GVariant *neighbors, *attr; gs_unref_variant GVariant *neighbor = NULL; neighbors = nm_lldp_listener_get_neighbors (listener); nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}")); g_assert_cmpint (g_variant_n_children (neighbors), ==, 1); neighbor = get_lldp_neighbor (neighbors, SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:02:03:04:05", SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/3"); g_assert (neighbor); g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 4); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "Port"); nm_clear_g_variant (&attr); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "SYS"); nm_clear_g_variant (&attr); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE); nm_clear_g_variant (&attr); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "foo"); nm_clear_g_variant (&attr); } TEST_RECV_DATA_DEFINE (_test_recv_data0, 1, _test_recv_data0_check, &_test_recv_data0_frame0); TEST_RECV_DATA_DEFINE (_test_recv_data0_twice, 1, _test_recv_data0_check, &_test_recv_data0_frame0, &_test_recv_data0_frame0); TEST_RECV_FRAME_DEFINE (_test_recv_data1_frame0, /* lldp.detailed.pcap from * https://wiki.wireshark.org/SampleCaptures#Link_Layer_Discovery_Protocol_.28LLDP.29 */ /* ethernet header */ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, /* destination mac */ 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, /* source mac */ 0x88, 0xcc, /* ethernet type */ 0x02, 0x07, 0x04, 0x00, 0x01, 0x30, /* Chassis Subtype */ 0xf9, 0xad, 0xa0, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x31, /* Port Subtype */ 0x06, 0x02, 0x00, 0x78, /* Time To Live */ 0x08, 0x17, 0x53, 0x75, 0x6d, 0x6d, /* Port Description */ 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x2d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x31, 0x30, 0x30, 0x31, 0x00, 0x0a, 0x0d, 0x53, 0x75, 0x6d, 0x6d, /* System Name */ 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x00, 0x0c, 0x4c, 0x53, 0x75, 0x6d, 0x6d, /* System Description */ 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x20, 0x2d, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x34, 0x65, 0x2e, 0x31, 0x20, 0x28, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x35, 0x29, 0x20, 0x62, 0x79, 0x20, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x30, 0x35, 0x2f, 0x32, 0x37, 0x2f, 0x30, 0x35, 0x20, 0x30, 0x34, 0x3a, 0x35, 0x33, 0x3a, 0x31, 0x31, 0x00, 0x0e, 0x04, 0x00, 0x14, 0x00, 0x14, /* Capabilities */ 0x10, 0x0e, 0x07, 0x06, 0x00, 0x01, /* Management Address */ 0x30, 0xf9, 0xad, 0xa0, 0x02, 0x00, 0x00, 0x03, 0xe9, 0x00, 0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02, /* IEEE 802.3 - Power Via MDI */ 0x07, 0x01, 0x00, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, /* IEEE 802.3 - MAC/PHY Configuration/Status */ 0x03, 0x6c, 0x00, 0x00, 0x10, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, /* IEEE 802.3 - Link Aggregation */ 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x12, 0x0f, 0x04, /* IEEE 802.3 - Maximum Frame Size */ 0x05, 0xf2, 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* IEEE 802.1 - Port VLAN ID */ 0x01, 0xe8, 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* IEEE 802.1 - Port and Protocol VLAN ID */ 0x01, 0x00, 0x00, 0xfe, 0x17, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */ 0x01, 0xe8, 0x10, 0x76, 0x32, 0x2d, 0x30, 0x34, 0x38, 0x38, 0x2d, 0x30, 0x33, 0x2d, 0x30, 0x35, 0x30, 0x35, 0x00, 0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, /* IEEE 802.1 - Protocol Identity */ 0x00, 0x00, 0x00 /* End of LLDPDU */ ); static void _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener) { GVariant *neighbors, *attr; gs_unref_variant GVariant *neighbor = NULL; neighbors = nm_lldp_listener_get_neighbors (listener); nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}")); g_assert_cmpint (g_variant_n_children (neighbors), ==, 1); neighbor = get_lldp_neighbor (neighbors, SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:30:F9:AD:A0", SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/1"); g_assert (neighbor); g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 10); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, NM_LLDP_DEST_NEAREST_BRIDGE); nm_clear_g_variant (&attr); /* unsupported: Time To Live */ /* Port Description */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "Summit300-48-Port 1001"); nm_clear_g_variant (&attr); /* System Name */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "Summit300-48"); nm_clear_g_variant (&attr); /* System Description */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "Summit300-48 - Version 7.4e.1 (Build 5) by Release_Master 05/27/05 04:53:11"); nm_clear_g_variant (&attr); /* Capabilities */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, G_VARIANT_TYPE_UINT32); nmtst_assert_variant_uint32 (attr, 20); nm_clear_g_variant (&attr); /* unsupported: Management Address */ /* unsupported: IEEE 802.3 - Power Via MDI */ /* unsupported: IEEE 802.3 - MAC/PHY Configuration/Status */ /* unsupported: IEEE 802.3 - Link Aggregation */ /* unsupported: IEEE 802.3 - Maximum Frame Size*/ /* IEEE 802.1 - Port VLAN ID */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PVID, G_VARIANT_TYPE_UINT32); nmtst_assert_variant_uint32 (attr, 488); nm_clear_g_variant (&attr); /* IEEE 802.1 - Port and Protocol VLAN ID */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID, G_VARIANT_TYPE_UINT32); nmtst_assert_variant_uint32 (attr, 0); nm_clear_g_variant (&attr); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, G_VARIANT_TYPE_UINT32); nmtst_assert_variant_uint32 (attr, 1); nm_clear_g_variant (&attr); /* IEEE 802.1 - VLAN Name */ attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, G_VARIANT_TYPE_STRING); nmtst_assert_variant_string (attr, "v2-0488-03-0505"); nm_clear_g_variant (&attr); attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VID, G_VARIANT_TYPE_UINT32); nmtst_assert_variant_uint32 (attr, 488); nm_clear_g_variant (&attr); /* unsupported: IEEE 802.1 - Protocol Identity */ } TEST_RECV_DATA_DEFINE (_test_recv_data1, 1, _test_recv_data1_check, &_test_recv_data1_frame0); TEST_RECV_FRAME_DEFINE (_test_recv_data2_frame0_ttl1, /* Ethernet header */ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ 0x88, 0xcc, /* Ethertype */ /* LLDP mandatory TLVs */ 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ 0x06, 0x02, 0x00, 0x01, /* TTL: 1 seconds */ /* LLDP optional TLVs */ 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */ 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */ 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */ 0x00, 0x00 /* End Of LLDPDU */ ); static void _test_recv_data2_ttl1_check (GMainLoop *loop, NMLldpListener *listener) { gulong notify_id; GVariant *neighbors; _test_recv_data0_check (loop, listener); /* wait for signal. */ notify_id = g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS, nmtst_main_loop_quit_on_notify, loop); if (!nmtst_main_loop_run (loop, 5000)) g_assert_not_reached (); nm_clear_g_signal_handler (listener, ¬ify_id); neighbors = nm_lldp_listener_get_neighbors (listener); nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}")); g_assert_cmpint (g_variant_n_children (neighbors), ==, 0); } TEST_RECV_DATA_DEFINE (_test_recv_data2_ttl1, 1, _test_recv_data2_ttl1_check, &_test_recv_data2_frame0_ttl1); static void _test_recv_fixture_setup (TestRecvFixture *fixture, gconstpointer user_data) { const NMPlatformLink *link; struct ifreq ifr = { }; int fd, s; fd = open ("/dev/net/tun", O_RDWR); g_assert (fd >= 0); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; nm_utils_ifname_cpy (ifr.ifr_name, TEST_IFNAME); g_assert (ioctl (fd, TUNSETIFF, &ifr) >= 0); /* Bring the interface up */ s = socket (AF_INET, SOCK_DGRAM, 0); g_assert (s >= 0); ifr.ifr_flags |= IFF_UP; g_assert (ioctl (s, SIOCSIFFLAGS, &ifr) >= 0); close (s); link = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TAP, 100); fixture->ifindex = link->ifindex; fixture->fd = fd; memcpy (fixture->mac, link->addr.data, ETH_ALEN); } typedef struct { int num_called; } TestRecvCallbackInfo; static void lldp_neighbors_changed (NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data) { TestRecvCallbackInfo *info = user_data; info->num_called++; } static void test_recv (TestRecvFixture *fixture, gconstpointer user_data) { const TestRecvData *data = user_data; gs_unref_object NMLldpListener *listener = NULL; GMainLoop *loop; TestRecvCallbackInfo info = { }; gsize i_frames; gulong notify_id; GError *error = NULL; guint sd_id; listener = nm_lldp_listener_new (); g_assert (listener != NULL); g_assert (nm_lldp_listener_start (listener, fixture->ifindex, &error)); g_assert_no_error (error); notify_id = g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS, (GCallback) lldp_neighbors_changed, &info); loop = g_main_loop_new (NULL, FALSE); sd_id = nm_sd_event_attach_default (); for (i_frames = 0; i_frames < data->frames_len; i_frames++) { const TestRecvFrame *f = data->frames[i_frames]; g_assert (write (fixture->fd, f->frame, f->frame_len) == f->frame_len); } if (nmtst_main_loop_run (loop, 500)) g_assert_not_reached (); g_assert_cmpint (info.num_called, ==, data->expected_num_called); nm_clear_g_signal_handler (listener, ¬ify_id); data->check (loop, listener); nm_clear_g_source (&sd_id); g_clear_pointer (&loop, g_main_loop_unref); } static void _test_recv_fixture_teardown (TestRecvFixture *fixture, gconstpointer user_data) { nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex); } /*****************************************************************************/ void _nmtstp_init_tests (int *argc, char ***argv) { nmtst_init_assert_logging (argc, argv, "WARN", "ALL"); } void _nmtstp_setup_tests (void) { #define _TEST_ADD_RECV(testpath, testdata) \ g_test_add (testpath, TestRecvFixture, testdata, _test_recv_fixture_setup, test_recv, _test_recv_fixture_teardown) _TEST_ADD_RECV ("/lldp/recv/0", &_test_recv_data0); _TEST_ADD_RECV ("/lldp/recv/0_twice", &_test_recv_data0_twice); _TEST_ADD_RECV ("/lldp/recv/1", &_test_recv_data1); _TEST_ADD_RECV ("/lldp/recv/2_ttl1", &_test_recv_data2_ttl1); }