/* * Netlink helper functions for driver wrappers * Copyright (c) 2002-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include "common.h" #include "eloop.h" #include "priv_netlink.h" #include "netlink.h" struct netlink_data { struct netlink_config *cfg; int sock; }; static void netlink_receive_link(struct netlink_data *netlink, void (*cb)(void *ctx, struct ifinfomsg *ifi, u8 *buf, size_t len), struct nlmsghdr *h) { if (cb == NULL || NLMSG_PAYLOAD(h, 0) < sizeof(struct ifinfomsg)) return; cb(netlink->cfg->ctx, NLMSG_DATA(h), (u8 *) NLMSG_DATA(h) + NLMSG_ALIGN(sizeof(struct ifinfomsg)), NLMSG_PAYLOAD(h, sizeof(struct ifinfomsg))); } static void netlink_receive(int sock, void *eloop_ctx, void *sock_ctx) { struct netlink_data *netlink = eloop_ctx; char buf[8192]; int left; struct sockaddr_nl from; socklen_t fromlen; struct nlmsghdr *h; int max_events = 10; try_again: fromlen = sizeof(from); left = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *) &from, &fromlen); if (left < 0) { if (errno != EINTR && errno != EAGAIN) wpa_printf(MSG_INFO, "netlink: recvfrom failed: %s", strerror(errno)); return; } h = (struct nlmsghdr *) buf; while (NLMSG_OK(h, left)) { switch (h->nlmsg_type) { case RTM_NEWLINK: netlink_receive_link(netlink, netlink->cfg->newlink_cb, h); break; case RTM_DELLINK: netlink_receive_link(netlink, netlink->cfg->dellink_cb, h); break; } h = NLMSG_NEXT(h, left); } if (left > 0) { wpa_printf(MSG_DEBUG, "netlink: %d extra bytes in the end of " "netlink message", left); } if (--max_events > 0) { /* * Try to receive all events in one eloop call in order to * limit race condition on cases where AssocInfo event, Assoc * event, and EAPOL frames are received more or less at the * same time. We want to process the event messages first * before starting EAPOL processing. */ goto try_again; } } struct netlink_data * netlink_init(struct netlink_config *cfg) { struct netlink_data *netlink; struct sockaddr_nl local; netlink = os_zalloc(sizeof(*netlink)); if (netlink == NULL) return NULL; netlink->sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (netlink->sock < 0) { wpa_printf(MSG_ERROR, "netlink: Failed to open netlink " "socket: %s", strerror(errno)); netlink_deinit(netlink); return NULL; } os_memset(&local, 0, sizeof(local)); local.nl_family = AF_NETLINK; local.nl_groups = RTMGRP_LINK; if (bind(netlink->sock, (struct sockaddr *) &local, sizeof(local)) < 0) { wpa_printf(MSG_ERROR, "netlink: Failed to bind netlink " "socket: %s", strerror(errno)); netlink_deinit(netlink); return NULL; } eloop_register_read_sock(netlink->sock, netlink_receive, netlink, NULL); netlink->cfg = cfg; return netlink; } void netlink_deinit(struct netlink_data *netlink) { if (netlink == NULL) return; if (netlink->sock >= 0) { eloop_unregister_read_sock(netlink->sock); close(netlink->sock); } os_free(netlink->cfg); os_free(netlink); } static const char * linkmode_str(int mode) { switch (mode) { case -1: return "no change"; case 0: return "kernel-control"; case 1: return "userspace-control"; } return "?"; } static const char * operstate_str(int state) { switch (state) { case -1: return "no change"; case IF_OPER_DORMANT: return "IF_OPER_DORMANT"; case IF_OPER_UP: return "IF_OPER_UP"; } return "?"; } int netlink_send_oper_ifla(struct netlink_data *netlink, int ifindex, int linkmode, int operstate) { struct { struct nlmsghdr hdr; struct ifinfomsg ifinfo; char opts[16]; } req; struct rtattr *rta; static int nl_seq; ssize_t ret; os_memset(&req, 0, sizeof(req)); req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.hdr.nlmsg_type = RTM_SETLINK; req.hdr.nlmsg_flags = NLM_F_REQUEST; req.hdr.nlmsg_seq = ++nl_seq; req.hdr.nlmsg_pid = 0; req.ifinfo.ifi_family = AF_UNSPEC; req.ifinfo.ifi_type = 0; req.ifinfo.ifi_index = ifindex; req.ifinfo.ifi_flags = 0; req.ifinfo.ifi_change = 0; if (linkmode != -1) { rta = aliasing_hide_typecast( ((char *) &req + NLMSG_ALIGN(req.hdr.nlmsg_len)), struct rtattr); rta->rta_type = IFLA_LINKMODE; rta->rta_len = RTA_LENGTH(sizeof(char)); *((char *) RTA_DATA(rta)) = linkmode; req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(sizeof(char)); } if (operstate != -1) { rta = aliasing_hide_typecast( ((char *) &req + NLMSG_ALIGN(req.hdr.nlmsg_len)), struct rtattr); rta->rta_type = IFLA_OPERSTATE; rta->rta_len = RTA_LENGTH(sizeof(char)); *((char *) RTA_DATA(rta)) = operstate; req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(sizeof(char)); } wpa_printf(MSG_DEBUG, "netlink: Operstate: ifindex=%d linkmode=%d (%s), operstate=%d (%s)", ifindex, linkmode, linkmode_str(linkmode), operstate, operstate_str(operstate)); ret = send(netlink->sock, &req, req.hdr.nlmsg_len, 0); if (ret < 0) { wpa_printf(MSG_DEBUG, "netlink: Sending operstate IFLA " "failed: %s (assume operstate is not supported)", strerror(errno)); } return ret < 0 ? -1 : 0; }