/* * WPA Supplicant - Layer2 packet handling with privilege separation * Copyright (c) 2007, 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 <sys/un.h> #include "common.h" #include "eloop.h" #include "l2_packet.h" #include "common/privsep_commands.h" struct l2_packet_data { int fd; /* UNIX domain socket for privsep access */ void (*rx_callback)(void *ctx, const u8 *src_addr, const u8 *buf, size_t len); void *rx_callback_ctx; u8 own_addr[ETH_ALEN]; char *own_socket_path; struct sockaddr_un priv_addr; }; static int wpa_priv_cmd(struct l2_packet_data *l2, int cmd, const void *data, size_t data_len) { struct msghdr msg; struct iovec io[2]; io[0].iov_base = &cmd; io[0].iov_len = sizeof(cmd); io[1].iov_base = (u8 *) data; io[1].iov_len = data_len; os_memset(&msg, 0, sizeof(msg)); msg.msg_iov = io; msg.msg_iovlen = data ? 2 : 1; msg.msg_name = &l2->priv_addr; msg.msg_namelen = sizeof(l2->priv_addr); if (sendmsg(l2->fd, &msg, 0) < 0) { wpa_printf(MSG_ERROR, "L2: sendmsg(cmd): %s", strerror(errno)); return -1; } return 0; } int l2_packet_get_own_addr(struct l2_packet_data *l2, u8 *addr) { os_memcpy(addr, l2->own_addr, ETH_ALEN); return 0; } int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto, const u8 *buf, size_t len) { struct msghdr msg; struct iovec io[4]; int cmd = PRIVSEP_CMD_L2_SEND; io[0].iov_base = &cmd; io[0].iov_len = sizeof(cmd); io[1].iov_base = &dst_addr; io[1].iov_len = ETH_ALEN; io[2].iov_base = &proto; io[2].iov_len = 2; io[3].iov_base = (u8 *) buf; io[3].iov_len = len; os_memset(&msg, 0, sizeof(msg)); msg.msg_iov = io; msg.msg_iovlen = 4; msg.msg_name = &l2->priv_addr; msg.msg_namelen = sizeof(l2->priv_addr); if (sendmsg(l2->fd, &msg, 0) < 0) { wpa_printf(MSG_ERROR, "L2: sendmsg(packet_send): %s", strerror(errno)); return -1; } return 0; } static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx) { struct l2_packet_data *l2 = eloop_ctx; u8 buf[2300]; int res; struct sockaddr_un from; socklen_t fromlen = sizeof(from); os_memset(&from, 0, sizeof(from)); res = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *) &from, &fromlen); if (res < 0) { wpa_printf(MSG_ERROR, "l2_packet_receive - recvfrom: %s", strerror(errno)); return; } if (res < ETH_ALEN) { wpa_printf(MSG_DEBUG, "L2: Too show packet received"); return; } if (from.sun_family != AF_UNIX || os_strncmp(from.sun_path, l2->priv_addr.sun_path, sizeof(from.sun_path)) != 0) { wpa_printf(MSG_DEBUG, "L2: Received message from unexpected " "source"); return; } l2->rx_callback(l2->rx_callback_ctx, buf, buf + ETH_ALEN, res - ETH_ALEN); } struct l2_packet_data * l2_packet_init( const char *ifname, const u8 *own_addr, unsigned short protocol, void (*rx_callback)(void *ctx, const u8 *src_addr, const u8 *buf, size_t len), void *rx_callback_ctx, int l2_hdr) { struct l2_packet_data *l2; char *own_dir = "/tmp"; char *priv_dir = "/var/run/wpa_priv"; size_t len; static unsigned int counter = 0; struct sockaddr_un addr; fd_set rfds; struct timeval tv; int res; u8 reply[ETH_ALEN + 1]; int reg_cmd[2]; l2 = os_zalloc(sizeof(struct l2_packet_data)); if (l2 == NULL) return NULL; l2->rx_callback = rx_callback; l2->rx_callback_ctx = rx_callback_ctx; len = os_strlen(own_dir) + 50; l2->own_socket_path = os_malloc(len); if (l2->own_socket_path == NULL) { os_free(l2); return NULL; } os_snprintf(l2->own_socket_path, len, "%s/wpa_privsep-l2-%d-%d", own_dir, getpid(), counter++); l2->priv_addr.sun_family = AF_UNIX; os_snprintf(l2->priv_addr.sun_path, sizeof(l2->priv_addr.sun_path), "%s/%s", priv_dir, ifname); l2->fd = socket(PF_UNIX, SOCK_DGRAM, 0); if (l2->fd < 0) { wpa_printf(MSG_ERROR, "socket(PF_UNIX): %s", strerror(errno)); os_free(l2->own_socket_path); l2->own_socket_path = NULL; os_free(l2); return NULL; } os_memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; os_strlcpy(addr.sun_path, l2->own_socket_path, sizeof(addr.sun_path)); if (bind(l2->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { wpa_printf(MSG_ERROR, "l2-pkt-privsep: bind(PF_UNIX): %s", strerror(errno)); goto fail; } reg_cmd[0] = protocol; reg_cmd[1] = l2_hdr; if (wpa_priv_cmd(l2, PRIVSEP_CMD_L2_REGISTER, reg_cmd, sizeof(reg_cmd)) < 0) { wpa_printf(MSG_ERROR, "L2: Failed to register with wpa_priv"); goto fail; } FD_ZERO(&rfds); FD_SET(l2->fd, &rfds); tv.tv_sec = 5; tv.tv_usec = 0; res = select(l2->fd + 1, &rfds, NULL, NULL, &tv); if (res < 0 && errno != EINTR) { wpa_printf(MSG_ERROR, "select: %s", strerror(errno)); goto fail; } if (FD_ISSET(l2->fd, &rfds)) { res = recv(l2->fd, reply, sizeof(reply), 0); if (res < 0) { wpa_printf(MSG_ERROR, "recv: %s", strerror(errno)); goto fail; } } else { wpa_printf(MSG_DEBUG, "L2: Timeout while waiting for " "registration reply"); goto fail; } if (res != ETH_ALEN) { wpa_printf(MSG_DEBUG, "L2: Unexpected registration reply " "(len=%d)", res); } os_memcpy(l2->own_addr, reply, ETH_ALEN); eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL); return l2; fail: close(l2->fd); l2->fd = -1; unlink(l2->own_socket_path); os_free(l2->own_socket_path); l2->own_socket_path = NULL; os_free(l2); return NULL; } struct l2_packet_data * l2_packet_init_bridge( const char *br_ifname, const char *ifname, const u8 *own_addr, unsigned short protocol, void (*rx_callback)(void *ctx, const u8 *src_addr, const u8 *buf, size_t len), void *rx_callback_ctx, int l2_hdr) { return l2_packet_init(br_ifname, own_addr, protocol, rx_callback, rx_callback_ctx, l2_hdr); } void l2_packet_deinit(struct l2_packet_data *l2) { if (l2 == NULL) return; if (l2->fd >= 0) { wpa_priv_cmd(l2, PRIVSEP_CMD_L2_UNREGISTER, NULL, 0); eloop_unregister_read_sock(l2->fd); close(l2->fd); } if (l2->own_socket_path) { unlink(l2->own_socket_path); os_free(l2->own_socket_path); } os_free(l2); } int l2_packet_get_ip_addr(struct l2_packet_data *l2, char *buf, size_t len) { /* TODO */ return -1; } void l2_packet_notify_auth_start(struct l2_packet_data *l2) { wpa_priv_cmd(l2, PRIVSEP_CMD_L2_NOTIFY_AUTH_START, NULL, 0); } int l2_packet_set_packet_filter(struct l2_packet_data *l2, enum l2_packet_filter_type type) { return -1; }