/* * * Ethernet daemon for Linux * * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "src/module.h" #include "src/eap.h" #include "wired/dbus.h" #include "wired/network.h" #include "wired/ethdev.h" #define ADAPTER_INTERFACE "net.connman.ead.Adapter" #define ADAPTER_BASEPATH EAD_BASE_PATH "/adapter" #define PROP_NAME "Name" #define PROP_ADDRESS "Address" #define PROP_ACTIVE "Active" #define PROP_CONNECTED "Connected" #define PROP_AUTHENTICATED "Authenticated" struct ethdev { uint32_t index; char ifname[IFNAMSIZ]; uint8_t addr[ETH_ALEN]; bool active; bool lower_up; bool auth_done; struct l_queue *eapol_sessions; char *path; }; struct eapol { struct ethdev *dev; uint8_t addr[ETH_ALEN]; struct eap_state *eap; struct l_settings *cred; }; static struct l_netlink *rtnl = NULL; static struct l_queue *ethdev_list = NULL; static char **whitelist_filter = NULL; static char **blacklist_filter = NULL; static struct l_io *pae_io; struct eapol_hdr { uint8_t proto_ver; uint8_t pkt_type; __be16 pkt_len; } __attribute__ ((packed)); static const uint8_t eapol_start[] = { 0x01, 0x01, 0x00, 0x00 }; static const uint8_t pae_group_addr[] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }; static bool pae_write(struct ethdev *dev, const uint8_t *addr, const uint8_t *frame, size_t len) { int fd = l_io_get_fd(pae_io); struct sockaddr_ll sll; ssize_t res; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_ifindex = dev->index; sll.sll_protocol = htons(ETH_P_PAE); sll.sll_halen = ETH_ALEN; memcpy(sll.sll_addr, addr, ETH_ALEN); res = sendto(fd, frame, len, 0, (struct sockaddr *) &sll, sizeof(sll)); if (res < 0) return false; return true; } static void eapol_free(void *data) { struct eapol *eapol = data; l_debug("Freeing EAPoL session"); eap_free(eapol->eap); l_settings_free(eapol->cred); l_free(eapol); } static bool eapol_match(const void *a, const void *b) { const struct eapol *eapol = a; return !memcmp(eapol->addr, b, ETH_ALEN); } static struct eapol *eapol_lookup(struct ethdev *dev, const uint8_t *addr) { return l_queue_find(dev->eapol_sessions, eapol_match, addr); } static bool ethdev_match(const void *a, const void *b) { const struct ethdev *dev = a; uint32_t index = L_PTR_TO_UINT(b); return (dev->index == index); } static struct ethdev *ethdev_lookup(uint32_t index) { return l_queue_find(ethdev_list, ethdev_match, L_UINT_TO_PTR(index)); } static void eap_tx_packet(const uint8_t *eap_data, size_t len, void *user_data) { struct eapol *eapol = user_data; uint8_t frame[1500]; l_put_u8(0x01, frame); l_put_u8(0x00, frame + 1); l_put_be16(len, frame + 2); memcpy(frame + 4, eap_data, len); /* * The supplicant / client always uses the PAE group address for * sending the EAP packets. */ pae_write(eapol->dev, pae_group_addr, frame, len + 4); } static void eap_complete(enum eap_result result, void *user_data) { struct eapol *eapol = user_data; struct ethdev *dev = eapol->dev; l_debug("result %u", result); if (result == EAP_RESULT_SUCCESS) { if (!dev->auth_done) { dev->auth_done = true; l_dbus_property_changed(dbus_app_get(), dev->path, ADAPTER_INTERFACE, PROP_AUTHENTICATED); } } l_queue_remove(dev->eapol_sessions, eapol); eapol_free(eapol); } static void eap_key_material(const uint8_t *msk_data, size_t msk_len, const uint8_t *emsk_data, size_t emsk_len, const uint8_t *iv, size_t iv_len, const uint8_t *session_id, size_t session_len, void *user_data) { l_debug("EAP key material received"); } static void eap_event(unsigned int event, const void *event_data, void *user_data) { l_debug("event %u", event); } static void rx_packet(struct ethdev *dev, const uint8_t *addr, const void *frame, size_t len) { const struct eapol_hdr *hdr = frame; struct eapol *eapol; uint16_t pkt_len; if (len < 4) { l_error("Too short EAPoL packet with %zu bytes", len); return; } pkt_len = L_BE16_TO_CPU(hdr->pkt_len); /* * EAPoL packet frames might contain padding at the end and so just * ensure that at least packet body length worth of packet body is * actually present. */ if (len - 4 < pkt_len) { l_error("Missing %zu bytes from EAPoL packet", pkt_len - (len - 4)); return; } switch (hdr->pkt_type) { case 0x00: /* EAP-Packet */ eapol = eapol_lookup(dev, addr); if (!eapol) { eapol = l_new(struct eapol, 1); eapol->dev = dev; memcpy(eapol->addr, addr, ETH_ALEN); eapol->eap = eap_new(eap_tx_packet, eap_complete, eapol); if (!eapol->eap) { l_error("Failed to create EAP instance"); l_free(eapol); return; } l_debug("Created new EAPoL session"); l_queue_push_tail(dev->eapol_sessions, eapol); eapol->cred = network_lookup_security("default"); eap_load_settings(eapol->eap, eapol->cred, "EAP-"); eap_set_key_material_func(eapol->eap, eap_key_material); eap_set_event_func(eapol->eap, eap_event); } eap_rx_packet(eapol->eap, frame + 4, pkt_len); break; } } /* * BPF filter to match skb->dev->type == 1 (ARPHRD_ETHER) and * match skb->protocol == 0x888e (PAE). */ static struct sock_filter pae_filter[] = { { 0x28, 0, 0, 0xfffff01c }, /* ldh #hatype */ { 0x15, 0, 3, 0x00000001 }, /* jne #1, drop */ { 0x28, 0, 0, 0xfffff000 }, /* ldh #proto */ { 0x15, 0, 1, 0x0000888e }, /* jne #0x888e, drop */ { 0x06, 0, 0, 0xffffffff }, /* keep: ret #-1 */ { 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */ }; static const struct sock_fprog pae_fprog = { .len = 6, .filter = pae_filter }; static bool pae_read(struct l_io *io, void *user_data) { int fd = l_io_get_fd(io); struct ethdev *dev; struct sockaddr_ll sll; socklen_t sll_len; ssize_t bytes; uint8_t frame[1500]; memset(&sll, 0, sizeof(sll)); sll_len = sizeof(sll); bytes = recvfrom(fd, frame, sizeof(frame), 0, (struct sockaddr *) &sll, &sll_len); if (bytes <= 0) { l_error("Reading from PAE socket failed: %s", strerror(errno)); return false; } if (sll.sll_hatype != ARPHRD_ETHER) return true; if (sll.sll_halen != ETH_ALEN) return true; if (ntohs(sll.sll_protocol) != ETH_P_PAE) return true; if (sll.sll_pkttype != PACKET_HOST && sll.sll_pkttype != PACKET_MULTICAST) return true; dev = ethdev_lookup(sll.sll_ifindex); if (!dev) return true; rx_packet(dev, sll.sll_addr, frame, bytes); return true; } static void pae_destroy() { pae_io = NULL; } static bool pae_open(void) { int fd; fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, htons(ETH_P_ALL)); if (fd < 0) return false; if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &pae_fprog, sizeof(pae_fprog)) < 0) { close(fd); return false; } pae_io = l_io_new(fd); l_io_set_close_on_destroy(pae_io, true); l_debug("Opened PAE socket"); l_io_set_read_handler(pae_io, pae_read, NULL, pae_destroy); return true; } static void pae_close(void) { l_io_destroy(pae_io); pae_io = NULL; l_debug("Closed PAE socket"); } static void do_debug(const char *str, void *user_data) { const char *prefix = user_data; l_info("%s%s", prefix, str); } static char *read_devtype_from_uevent(const char *ifname) { char line[128], *filename, *devtype = NULL; FILE *f; if (!ifname) return NULL; filename = l_strdup_printf("/sys/class/net/%s/uevent", ifname); f = fopen(filename, "re"); l_free(filename); if (!f) return NULL; while (fgets(line, sizeof(line), f)) { char *pos; pos = strchr(line, '\n'); if (!pos) continue; pos[0] = '\0'; if (!strncmp(line, "DEVTYPE=", 8)) { devtype = l_strdup(line + 8); break; } } fclose(f); return devtype; } static int modify_membership(struct ethdev *dev, int optname) { struct packet_mreq mreq; int fd; fd = l_io_get_fd(pae_io); if (fd < 0) return -1; memset(&mreq, 0, sizeof(mreq)); mreq.mr_ifindex = dev->index; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; memcpy(mreq.mr_address, pae_group_addr, ETH_ALEN); return setsockopt(fd, SOL_PACKET, optname, &mreq, sizeof(mreq)); } static void ethdev_free(void *data) { struct ethdev *dev = data; l_debug("Freeing device %s", dev->ifname); modify_membership(dev, PACKET_DROP_MEMBERSHIP); l_queue_destroy(dev->eapol_sessions, eapol_free); l_dbus_object_remove_interface(dbus_app_get(), dev->path, ADAPTER_INTERFACE); l_free(dev->path); l_free(dev); } static bool is_ifname_valid(const char *ifname) { char *pattern; unsigned int i; if (!whitelist_filter) goto check_blacklist; for (i = 0; (pattern = whitelist_filter[i]); i++) { if (fnmatch(pattern, ifname, 0)) continue; goto check_blacklist; } return false; check_blacklist: if (!blacklist_filter) return true; for (i = 0; (pattern = blacklist_filter[i]); i++) { if (!fnmatch(pattern, ifname, 0)) return false; } return true; } static void newlink_notify(const struct ifinfomsg *ifi, int bytes) { uint32_t index = ifi->ifi_index; struct ethdev *dev; const struct rtattr *attr; uint8_t *addr = NULL; const char *ifname = NULL; uint8_t linkmode = 0, operstate = 0; bool active, lower_up, lower_changed = false; if (ifi->ifi_type != ARPHRD_ETHER) return; active = ifi->ifi_flags & IFF_UP; lower_up = ifi->ifi_flags & IFF_LOWER_UP; for (attr = IFLA_RTA(ifi); RTA_OK(attr, bytes); attr = RTA_NEXT(attr, bytes)) { switch (attr->rta_type) { case IFLA_ADDRESS: if (RTA_PAYLOAD(attr) == ETH_ALEN) addr = RTA_DATA(attr); break; case IFLA_IFNAME: ifname = RTA_DATA(attr); break; case IFLA_LINKMODE: linkmode = l_get_u8(RTA_DATA(attr)); break; case IFLA_OPERSTATE: operstate = l_get_u8(RTA_DATA(attr)); break; } } if (!addr || !ifname) return; l_debug("%s: linkmode %u operstate %u", ifname, linkmode, operstate); if (!is_ifname_valid(ifname)) { l_debug("Ignoring device with interface name %s", ifname); return; } dev = ethdev_lookup(index); if (!dev) { char *devtype; /* * If there is no existing Ethernet device structure, then * first check uevent if this is wired Ethernet or not. */ devtype = read_devtype_from_uevent(ifname); if (devtype) { l_free(devtype); return; } if (l_queue_isempty(ethdev_list)) { if (!pae_open()) { l_error("Failed to open PAE port"); return; } } dev = l_new(struct ethdev, 1); dev->index = index; dev->active = active; dev->lower_up = lower_up; dev->auth_done = false; dev->eapol_sessions = l_queue_new(); dev->path = l_strdup_printf("%s/%u", ADAPTER_BASEPATH, dev->index); l_debug("Creating device %u", dev->index); modify_membership(dev, PACKET_ADD_MEMBERSHIP); l_dbus_object_add_interface(dbus_app_get(), dev->path, ADAPTER_INTERFACE, dev); l_dbus_object_add_interface(dbus_app_get(), dev->path, L_DBUS_INTERFACE_PROPERTIES, NULL); l_queue_push_tail(ethdev_list, dev); lower_changed = true; } if (ifname) strcpy(dev->ifname, ifname); memcpy(dev->addr, addr, ETH_ALEN); if (active != dev->active) { dev->active = active; l_dbus_property_changed(dbus_app_get(), dev->path, ADAPTER_INTERFACE, PROP_ACTIVE); } if (lower_up != dev->lower_up) { if (!lower_up) { dev->auth_done = false; l_dbus_property_changed(dbus_app_get(), dev->path, ADAPTER_INTERFACE, PROP_AUTHENTICATED); } dev->lower_up = lower_up; l_dbus_property_changed(dbus_app_get(), dev->path, ADAPTER_INTERFACE, PROP_CONNECTED); lower_changed = true; } if (lower_changed) { if (dev->lower_up) pae_write(dev, pae_group_addr, eapol_start, sizeof(eapol_start)); else l_queue_clear(dev->eapol_sessions, eapol_free); } } static void dellink_notify(const struct ifinfomsg *ifi, int bytes) { uint32_t index = ifi->ifi_index; struct ethdev *dev; if (ifi->ifi_type != ARPHRD_ETHER) return; dev = l_queue_remove_if(ethdev_list, ethdev_match, L_UINT_TO_PTR(index)); if (!dev) return; l_debug("Removing device %u", dev->index); ethdev_free(dev); if (l_queue_isempty(ethdev_list)) pae_close(); } static void link_notify(uint16_t type, const void *data, uint32_t len, void *user_data) { const struct ifinfomsg *ifi = data; unsigned int bytes; if (ifi->ifi_type != ARPHRD_ETHER) return; bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg)); switch (type) { case RTM_NEWLINK: newlink_notify(ifi, bytes); break; case RTM_DELLINK: dellink_notify(ifi, bytes); break; } } static void getlink_callback(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { const struct ifinfomsg *ifi = data; unsigned int bytes; if (error) { l_error("Failure with link information dump (%d)", error); return; } if (type != RTM_NEWLINK) return; bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg)); newlink_notify(ifi, bytes); } static bool property_get_name(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct ethdev *dev = user_data; l_dbus_message_builder_append_basic(builder, 's', dev->ifname); return true; } static bool property_get_address(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct ethdev *dev = user_data; char str[18]; sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", dev->addr[0], dev->addr[1], dev->addr[2], dev->addr[3], dev->addr[4], dev->addr[5]); l_dbus_message_builder_append_basic(builder, 's', str); return true; } static bool property_get_active(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct ethdev *dev = user_data; bool active = dev->active; l_dbus_message_builder_append_basic(builder, 'b', &active); return true; } static bool property_get_connected(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct ethdev *dev = user_data; bool connected = dev->lower_up; l_dbus_message_builder_append_basic(builder, 'b', &connected); return true; } static bool property_get_authenticated(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct ethdev *dev = user_data; bool authenticated = dev->auth_done; l_dbus_message_builder_append_basic(builder, 'b', &authenticated); return true; } static void setup_adapter_interface(struct l_dbus_interface *interface) { l_dbus_interface_property(interface, PROP_NAME, 0, "s", property_get_name, NULL); l_dbus_interface_property(interface, PROP_ADDRESS, 0, "s", property_get_address, NULL); l_dbus_interface_property(interface, PROP_ACTIVE, 0, "b", property_get_active, NULL); l_dbus_interface_property(interface, PROP_CONNECTED, 0, "b", property_get_connected, NULL); l_dbus_interface_property(interface, PROP_AUTHENTICATED, 0, "b", property_get_authenticated, NULL); } bool ethdev_init(const char *whitelist, const char *blacklist) { struct ifinfomsg msg; if (rtnl) return false; l_debug("Opening route netlink socket"); rtnl = l_netlink_new(NETLINK_ROUTE); if (!rtnl) { l_error("Failed to open route netlink socket"); return false; } if (getenv("EAD_RTNL_DEBUG")) l_netlink_set_debug(rtnl, do_debug, "[RTNL] ", NULL); if (!l_netlink_register(rtnl, RTNLGRP_LINK, link_notify, NULL, NULL)) { l_error("Failed to register for RTNL link notifications"); l_netlink_destroy(rtnl); return false; } ethdev_list = l_queue_new(); if (!l_dbus_register_interface(dbus_app_get(), ADAPTER_INTERFACE, setup_adapter_interface, NULL, false)) { l_error("Unable to register the adapter interface"); l_netlink_destroy(rtnl); return false; } if (whitelist) whitelist_filter = l_strsplit(whitelist, ','); if (blacklist) blacklist_filter = l_strsplit(blacklist, ','); memset(&msg, 0, sizeof(msg)); l_netlink_send(rtnl, RTM_GETLINK, NLM_F_DUMP, &msg, sizeof(msg), getlink_callback, NULL, NULL); return true; } void ethdev_exit(void) { if (!rtnl) return; l_dbus_unregister_interface(dbus_app_get(), ADAPTER_INTERFACE); pae_close(); l_strfreev(whitelist_filter); l_strfreev(blacklist_filter); l_debug("Closing route netlink socket"); l_netlink_destroy(rtnl); rtnl = NULL; l_queue_destroy(ethdev_list, ethdev_free); ethdev_list = NULL; }