| /* |
| * hostapd / IEEE 802 OUI Extended EtherType 88-B7 |
| * Copyright (c) 2016, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "utils/includes.h" |
| |
| #include "utils/common.h" |
| #include "utils/eloop.h" |
| #include "l2_packet/l2_packet.h" |
| #include "hostapd.h" |
| #include "eth_p_oui.h" |
| |
| /* |
| * See IEEE Std 802-2014, Clause 9.2.4 for the definition of the OUI Extended |
| * EtherType 88-B7. This file implements this with OUI 00:13:74 and |
| * vendor-specific subtype 0x0001. |
| */ |
| static const u8 global_oui[] = { 0x00, 0x13, 0x74, 0x00, 0x01 }; |
| |
| struct eth_p_oui_iface { |
| struct dl_list list; |
| char ifname[IFNAMSIZ + 1]; |
| struct l2_packet_data *l2; |
| struct dl_list receiver; |
| }; |
| |
| struct eth_p_oui_ctx { |
| struct dl_list list; |
| struct eth_p_oui_iface *iface; |
| /* all data needed to deliver and unregister */ |
| u8 oui_suffix; /* last byte of OUI */ |
| void (*rx_callback)(void *ctx, const u8 *src_addr, |
| const u8 *dst_addr, u8 oui_suffix, |
| const u8 *buf, size_t len); |
| void *rx_callback_ctx; |
| }; |
| |
| |
| void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr, |
| const u8 *dst_addr, const u8 *buf, size_t len) |
| { |
| ctx->rx_callback(ctx->rx_callback_ctx, src_addr, dst_addr, |
| ctx->oui_suffix, buf, len); |
| } |
| |
| |
| static void eth_p_rx(void *ctx, const u8 *src_addr, const u8 *buf, size_t len) |
| { |
| struct eth_p_oui_iface *iface = ctx; |
| struct eth_p_oui_ctx *receiver; |
| const struct l2_ethhdr *ethhdr; |
| |
| if (len < sizeof(*ethhdr) + sizeof(global_oui) + 1) { |
| /* too short packet */ |
| return; |
| } |
| |
| ethhdr = (struct l2_ethhdr *) buf; |
| /* trim eth_hdr from buf and len */ |
| buf += sizeof(*ethhdr); |
| len -= sizeof(*ethhdr); |
| |
| /* verify OUI and vendor-specific subtype match */ |
| if (os_memcmp(buf, global_oui, sizeof(global_oui)) != 0) |
| return; |
| buf += sizeof(global_oui); |
| len -= sizeof(global_oui); |
| |
| dl_list_for_each(receiver, &iface->receiver, |
| struct eth_p_oui_ctx, list) { |
| if (buf[0] != receiver->oui_suffix) |
| continue; |
| |
| eth_p_oui_deliver(receiver, ethhdr->h_source, ethhdr->h_dest, |
| buf + 1, len - 1); |
| } |
| } |
| |
| |
| struct eth_p_oui_ctx * |
| eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix, |
| void (*rx_callback)(void *ctx, const u8 *src_addr, |
| const u8 *dst_addr, u8 oui_suffix, |
| const u8 *buf, size_t len), |
| void *rx_callback_ctx) |
| { |
| struct eth_p_oui_iface *iface; |
| struct eth_p_oui_ctx *receiver; |
| int found = 0; |
| struct hapd_interfaces *interfaces; |
| |
| receiver = os_zalloc(sizeof(*receiver)); |
| if (!receiver) |
| goto err; |
| |
| receiver->oui_suffix = oui_suffix; |
| receiver->rx_callback = rx_callback; |
| receiver->rx_callback_ctx = rx_callback_ctx; |
| |
| interfaces = hapd->iface->interfaces; |
| |
| dl_list_for_each(iface, &interfaces->eth_p_oui, struct eth_p_oui_iface, |
| list) { |
| if (os_strcmp(iface->ifname, ifname) != 0) |
| continue; |
| found = 1; |
| break; |
| } |
| |
| if (!found) { |
| iface = os_zalloc(sizeof(*iface)); |
| if (!iface) |
| goto err; |
| |
| os_strlcpy(iface->ifname, ifname, sizeof(iface->ifname)); |
| iface->l2 = l2_packet_init(ifname, NULL, ETH_P_OUI, eth_p_rx, |
| iface, 1); |
| if (!iface->l2) { |
| os_free(iface); |
| goto err; |
| } |
| dl_list_init(&iface->receiver); |
| |
| dl_list_add_tail(&interfaces->eth_p_oui, &iface->list); |
| } |
| |
| dl_list_add_tail(&iface->receiver, &receiver->list); |
| receiver->iface = iface; |
| |
| return receiver; |
| err: |
| os_free(receiver); |
| return NULL; |
| } |
| |
| |
| void eth_p_oui_unregister(struct eth_p_oui_ctx *ctx) |
| { |
| struct eth_p_oui_iface *iface; |
| |
| if (!ctx) |
| return; |
| |
| iface = ctx->iface; |
| |
| dl_list_del(&ctx->list); |
| os_free(ctx); |
| |
| if (dl_list_empty(&iface->receiver)) { |
| dl_list_del(&iface->list); |
| l2_packet_deinit(iface->l2); |
| os_free(iface); |
| } |
| } |
| |
| |
| int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr, |
| const u8 *dst_addr, const u8 *buf, size_t len) |
| { |
| struct eth_p_oui_iface *iface = ctx->iface; |
| u8 *packet, *p; |
| size_t packet_len; |
| int ret; |
| struct l2_ethhdr *ethhdr; |
| |
| packet_len = sizeof(*ethhdr) + sizeof(global_oui) + 1 + len; |
| packet = os_zalloc(packet_len); |
| if (!packet) |
| return -1; |
| p = packet; |
| |
| ethhdr = (struct l2_ethhdr *) packet; |
| os_memcpy(ethhdr->h_source, src_addr, ETH_ALEN); |
| os_memcpy(ethhdr->h_dest, dst_addr, ETH_ALEN); |
| ethhdr->h_proto = host_to_be16(ETH_P_OUI); |
| p += sizeof(*ethhdr); |
| |
| os_memcpy(p, global_oui, sizeof(global_oui)); |
| p[sizeof(global_oui)] = ctx->oui_suffix; |
| p += sizeof(global_oui) + 1; |
| |
| os_memcpy(p, buf, len); |
| |
| ret = l2_packet_send(iface->l2, NULL, 0, packet, packet_len); |
| os_free(packet); |
| return ret; |
| } |