| /* | |
| * Copyright (C) 2016 David Decotigny <ddecotig@gmail.com> | |
| * | |
| * 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 of the | |
| * License, or 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 St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| */ | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <ipxe/linux_api.h> | |
| #include <ipxe/list.h> | |
| #include <ipxe/linux.h> | |
| #include <ipxe/malloc.h> | |
| #include <ipxe/device.h> | |
| #include <ipxe/netdevice.h> | |
| #include <ipxe/iobuf.h> | |
| #include <ipxe/ethernet.h> | |
| #include <ipxe/settings.h> | |
| #include <ipxe/socket.h> | |
| /* This hack prevents pre-2.6.32 headers from redefining struct sockaddr */ | |
| #define _SYS_SOCKET_H | |
| #define __GLIBC__ 2 | |
| #include <linux/socket.h> | |
| #include <linux/if.h> | |
| #include <linux/if_ether.h> | |
| #include <linux/if_packet.h> | |
| #undef __GLIBC__ | |
| #include <byteswap.h> | |
| /* linux-specifc syscall params */ | |
| #define LINUX_AF_PACKET 17 | |
| #define LINUX_SOCK_RAW 3 | |
| #define LINUX_SIOCGIFINDEX 0x8933 | |
| #define LINUX_SIOCGIFHWADDR 0x8927 | |
| #define RX_BUF_SIZE 1536 | |
| /** @file | |
| * | |
| * The AF_PACKET driver. | |
| * | |
| * Bind to an existing linux network interface. | |
| */ | |
| struct af_packet_nic { | |
| /** Linux network interface name */ | |
| char * ifname; | |
| /** Packet socket descriptor */ | |
| int fd; | |
| /** ifindex */ | |
| int ifindex; | |
| }; | |
| /** Open the linux interface */ | |
| static int af_packet_nic_open ( struct net_device * netdev ) | |
| { | |
| struct af_packet_nic * nic = netdev->priv; | |
| struct sockaddr_ll socket_address; | |
| struct ifreq if_data; | |
| int ret; | |
| nic->fd = linux_socket(LINUX_AF_PACKET, LINUX_SOCK_RAW, | |
| htons(ETH_P_ALL)); | |
| if (nic->fd < 0) { | |
| DBGC(nic, "af_packet %p socket(AF_PACKET) = %d (%s)\n", | |
| nic, nic->fd, linux_strerror(linux_errno)); | |
| return nic->fd; | |
| } | |
| /* resolve ifindex of ifname */ | |
| memset(&if_data, 0, sizeof(if_data)); | |
| strncpy(if_data.ifr_name, nic->ifname, sizeof(if_data.ifr_name)); | |
| ret = linux_ioctl(nic->fd, LINUX_SIOCGIFINDEX, &if_data); | |
| if (ret < 0) { | |
| DBGC(nic, "af_packet %p ioctl(SIOCGIFINDEX) = %d (%s)\n", | |
| nic, ret, linux_strerror(linux_errno)); | |
| linux_close(nic->fd); | |
| return ret; | |
| } | |
| nic->ifindex = if_data.ifr_ifindex; | |
| /* bind to interface */ | |
| memset(&socket_address, 0, sizeof(socket_address)); | |
| socket_address.sll_family = LINUX_AF_PACKET; | |
| socket_address.sll_ifindex = nic->ifindex; | |
| socket_address.sll_protocol = htons(ETH_P_ALL); | |
| ret = linux_bind(nic->fd, (void *) &socket_address, | |
| sizeof(socket_address)); | |
| if (ret == -1) { | |
| DBGC(nic, "af_packet %p bind() = %d (%s)\n", | |
| nic, ret, linux_strerror(linux_errno)); | |
| linux_close(nic->fd); | |
| return ret; | |
| } | |
| /* Set nonblocking mode to make af_packet_nic_poll() easier */ | |
| ret = linux_fcntl(nic->fd, F_SETFL, O_NONBLOCK); | |
| if (ret != 0) { | |
| DBGC(nic, "af_packet %p fcntl(%d, ...) = %d (%s)\n", | |
| nic, nic->fd, ret, linux_strerror(linux_errno)); | |
| linux_close(nic->fd); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /** Close the packet socket */ | |
| static void af_packet_nic_close ( struct net_device *netdev ) | |
| { | |
| struct af_packet_nic * nic = netdev->priv; | |
| linux_close(nic->fd); | |
| } | |
| /** | |
| * Transmit an ethernet packet. | |
| * | |
| * The packet can be written to the socket and marked as complete immediately. | |
| */ | |
| static int af_packet_nic_transmit ( struct net_device *netdev, | |
| struct io_buffer *iobuf ) | |
| { | |
| struct af_packet_nic * nic = netdev->priv; | |
| struct sockaddr_ll socket_address; | |
| const struct ethhdr * eh; | |
| int rc; | |
| memset(&socket_address, 0, sizeof(socket_address)); | |
| socket_address.sll_family = LINUX_AF_PACKET; | |
| socket_address.sll_ifindex = nic->ifindex; | |
| socket_address.sll_halen = ETH_ALEN; | |
| eh = iobuf->data; | |
| memcpy(socket_address.sll_addr, eh->h_dest, ETH_ALEN); | |
| rc = linux_sendto(nic->fd, iobuf->data, iobuf->tail - iobuf->data, | |
| 0, (struct sockaddr *)&socket_address, | |
| sizeof(socket_address)); | |
| DBGC2(nic, "af_packet %p wrote %d bytes\n", nic, rc); | |
| netdev_tx_complete(netdev, iobuf); | |
| return 0; | |
| } | |
| /** Poll for new packets */ | |
| static void af_packet_nic_poll ( struct net_device *netdev ) | |
| { | |
| struct af_packet_nic * nic = netdev->priv; | |
| struct pollfd pfd; | |
| struct io_buffer * iobuf; | |
| int r; | |
| pfd.fd = nic->fd; | |
| pfd.events = POLLIN; | |
| if (linux_poll(&pfd, 1, 0) == -1) { | |
| DBGC(nic, "af_packet %p poll failed (%s)\n", | |
| nic, linux_strerror(linux_errno)); | |
| return; | |
| } | |
| if ((pfd.revents & POLLIN) == 0) | |
| return; | |
| /* At this point we know there is at least one new packet to be read */ | |
| iobuf = alloc_iob(RX_BUF_SIZE); | |
| if (! iobuf) | |
| goto allocfail; | |
| while ((r = linux_read(nic->fd, iobuf->data, RX_BUF_SIZE)) > 0) { | |
| DBGC2(nic, "af_packet %p read %d bytes\n", nic, r); | |
| iob_put(iobuf, r); | |
| netdev_rx(netdev, iobuf); | |
| iobuf = alloc_iob(RX_BUF_SIZE); | |
| if (! iobuf) | |
| goto allocfail; | |
| } | |
| free_iob(iobuf); | |
| return; | |
| allocfail: | |
| DBGC(nic, "af_packet %p alloc_iob failed\n", nic); | |
| } | |
| /** | |
| * Set irq. | |
| * | |
| * Not used on linux, provide a dummy implementation. | |
| */ | |
| static void af_packet_nic_irq ( struct net_device *netdev, int enable ) | |
| { | |
| struct af_packet_nic *nic = netdev->priv; | |
| DBGC(nic, "af_packet %p irq enable = %d\n", nic, enable); | |
| } | |
| static int af_packet_update_properties ( struct net_device *netdev ) | |
| { | |
| struct af_packet_nic *nic = netdev->priv; | |
| struct ifreq if_data; | |
| int ret; | |
| /* retrieve default MAC address */ | |
| int fd = linux_socket(LINUX_AF_PACKET, LINUX_SOCK_RAW, 0); | |
| if (fd < 0) { | |
| DBGC(nic, "af_packet %p cannot create raw socket (%s)\n", | |
| nic, linux_strerror(linux_errno)); | |
| return fd; | |
| } | |
| /* retrieve host's MAC address */ | |
| memset(&if_data, 0, sizeof(if_data)); | |
| strncpy(if_data.ifr_name, nic->ifname, sizeof(if_data.ifr_name)); | |
| ret = linux_ioctl(fd, LINUX_SIOCGIFHWADDR, &if_data); | |
| if (ret < 0) { | |
| DBGC(nic, "af_packet %p cannot get mac addr (%s)\n", | |
| nic, linux_strerror(linux_errno)); | |
| linux_close(fd); | |
| return ret; | |
| } | |
| linux_close(fd); | |
| /* struct sockaddr = { u16 family, u8 pad[14] (equiv. sa_data) }; */ | |
| memcpy(netdev->ll_addr, if_data.ifr_hwaddr.pad, ETH_ALEN); | |
| return 0; | |
| } | |
| /** AF_PACKET operations */ | |
| static struct net_device_operations af_packet_nic_operations = { | |
| .open = af_packet_nic_open, | |
| .close = af_packet_nic_close, | |
| .transmit = af_packet_nic_transmit, | |
| .poll = af_packet_nic_poll, | |
| .irq = af_packet_nic_irq, | |
| }; | |
| /** Handle a device request for the af_packet driver */ | |
| static int af_packet_nic_probe ( struct linux_device *device, | |
| struct linux_device_request *request ) | |
| { | |
| struct linux_setting *if_setting; | |
| struct net_device *netdev; | |
| struct af_packet_nic *nic; | |
| int rc; | |
| netdev = alloc_etherdev(sizeof(*nic)); | |
| if (! netdev) | |
| return -ENOMEM; | |
| netdev_init(netdev, &af_packet_nic_operations); | |
| nic = netdev->priv; | |
| linux_set_drvdata(device, netdev); | |
| netdev->dev = &device->dev; | |
| memset(nic, 0, sizeof(*nic)); | |
| /* Look for the mandatory if setting */ | |
| if_setting = linux_find_setting("if", &request->settings); | |
| /* No if setting */ | |
| if (! if_setting) { | |
| printf("af_packet missing a mandatory if setting\n"); | |
| rc = -EINVAL; | |
| goto err_settings; | |
| } | |
| nic->ifname = if_setting->value; | |
| snprintf ( device->dev.name, sizeof ( device->dev.name ), "%s", | |
| nic->ifname ); | |
| device->dev.desc.bus_type = BUS_TYPE_TAP; | |
| af_packet_update_properties(netdev); | |
| if_setting->applied = 1; | |
| /* Apply rest of the settings */ | |
| linux_apply_settings(&request->settings, &netdev->settings.settings); | |
| /* Register network device */ | |
| if ((rc = register_netdev(netdev)) != 0) | |
| goto err_register; | |
| netdev_link_up(netdev); | |
| return 0; | |
| unregister_netdev(netdev); | |
| err_register: | |
| err_settings: | |
| netdev_nullify(netdev); | |
| netdev_put(netdev); | |
| return rc; | |
| } | |
| /** Remove the device */ | |
| static void af_packet_nic_remove ( struct linux_device *device ) | |
| { | |
| struct net_device *netdev = linux_get_drvdata(device); | |
| unregister_netdev(netdev); | |
| netdev_nullify(netdev); | |
| netdev_put(netdev); | |
| } | |
| /** AF_PACKET linux_driver */ | |
| struct linux_driver af_packet_nic_driver __linux_driver = { | |
| .name = "af_packet", | |
| .probe = af_packet_nic_probe, | |
| .remove = af_packet_nic_remove, | |
| }; | |
| /* Generate build rules */ | |
| LINUX_ROM ( "af_packet", "AF_PACKET binding NIC" ); |