diff options
author | Michael Brown <mcb30@ipxe.org> | 2021-03-02 10:20:55 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2021-03-02 11:09:57 +0000 |
commit | 2b5d3f582f718ca11488fb6d92ea39dd22b8ffed (patch) | |
tree | a4ab380c613b7fa3261f7538fc8524b04b610dc0 /src/drivers/linux | |
parent | 916ebef1984e9caa05646463734b51202b405774 (diff) | |
download | ipxe-2b5d3f582f718ca11488fb6d92ea39dd22b8ffed.tar.gz |
[slirp] Add libslirp driver for Linux
Add a driver using libslirp to provide a virtual network interface
without requiring root permissions on the host. This simplifies the
process of running iPXE as a Linux userspace application with network
access. For example:
make bin-x86_64-linux/slirp.linux
./bin-x86_64-linux/slirp.linux --net slirp
libslirp will provide a built-in emulated DHCP server and NAT router.
Settings such as the boot filename may be controlled via command-line
options. For example:
./bin-x86_64-linux/slirp.linux \
--net slirp,filename=http://192.168.0.1/boot.ipxe
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/linux')
-rw-r--r-- | src/drivers/linux/slirp.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/src/drivers/linux/slirp.c b/src/drivers/linux/slirp.c new file mode 100644 index 000000000..8341c9676 --- /dev/null +++ b/src/drivers/linux/slirp.c @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2021 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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 Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include <stdio.h> +#include <errno.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/if_ether.h> +#include <ipxe/in.h> +#include <ipxe/timer.h> +#include <ipxe/retry.h> +#include <ipxe/linux.h> +#include <ipxe/linux_api.h> +#include <ipxe/slirp.h> + +/** @file + * + * Linux Slirp network driver + * + */ + +/** Maximum number of open file descriptors */ +#define SLIRP_MAX_FDS 128 + +/** A Slirp network interface */ +struct slirp_nic { + /** The libslirp device object */ + struct Slirp *slirp; + /** Polling file descriptor list */ + struct pollfd pollfds[SLIRP_MAX_FDS]; + /** Number of file descriptors */ + unsigned int numfds; +}; + +/** A Slirp alarm timer */ +struct slirp_alarm { + /** Slirp network interface */ + struct slirp_nic *slirp; + /** Retry timer */ + struct retry_timer timer; + /** Callback function */ + void ( __asmcall * callback ) ( void *opaque ); + /** Opaque value for callback function */ + void *opaque; +}; + +/** Default MAC address */ +static const uint8_t slirp_default_mac[ETH_ALEN] = + { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }; + +/****************************************************************************** + * + * Slirp interface + * + ****************************************************************************** + */ + +/** + * Send packet + * + * @v buf Data buffer + * @v len Length of data + * @v device Device opaque pointer + * @ret len Consumed length (or negative on error) + */ +static ssize_t __asmcall slirp_send_packet ( const void *buf, size_t len, + void *device ) { + struct net_device *netdev = device; + struct io_buffer *iobuf; + + /* Allocate I/O buffer */ + iobuf = alloc_iob ( len ); + if ( ! iobuf ) + return -1; + + /* Populate I/O buffer */ + memcpy ( iob_put ( iobuf, len ), buf, len ); + + /* Hand off to network stack */ + netdev_rx ( netdev, iobuf ); + + return len; +} + +/** + * Print an error message + * + * @v msg Error message + * @v device Device opaque pointer + */ +static void __asmcall slirp_guest_error ( const char *msg, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + + DBGC ( slirp, "SLIRP %p error: %s\n", slirp, msg ); +} + +/** + * Get virtual clock + * + * @v device Device opaque pointer + * @ret clock_ns Clock time in nanoseconds + */ +static int64_t __asmcall slirp_clock_get_ns ( void *device __unused ) { + int64_t time; + + time = currticks(); + return ( time * ( 1000000 / TICKS_PER_MS ) ); +} + +/** + * Handle timer expiry + * + * @v timer Retry timer + * @v over Failure indicator + */ +static void slirp_expired ( struct retry_timer *timer, int over __unused ) { + struct slirp_alarm *alarm = + container_of ( timer, struct slirp_alarm, timer ); + struct slirp_nic *slirp = alarm->slirp; + + /* Notify callback */ + DBGC ( slirp, "SLIRP %p timer fired\n", slirp ); + alarm->callback ( alarm->opaque ); +} + +/** + * Create a new timer + * + * @v callback Timer callback + * @v opaque Timer opaque pointer + * @v device Device opaque pointer + * @ret timer Timer + */ +static void * __asmcall +slirp_timer_new ( void ( __asmcall * callback ) ( void *opaque ), + void *opaque, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + struct slirp_alarm *alarm; + + /* Allocate timer */ + alarm = malloc ( sizeof ( *alarm ) ); + if ( ! alarm ) { + DBGC ( slirp, "SLIRP %p could not allocate timer\n", slirp ); + return NULL; + } + + /* Initialise timer */ + memset ( alarm, 0, sizeof ( *alarm ) ); + alarm->slirp = slirp; + timer_init ( &alarm->timer, slirp_expired, NULL ); + alarm->callback = callback; + alarm->opaque = opaque; + DBGC ( slirp, "SLIRP %p timer %p has callback %p (%p)\n", + slirp, alarm, alarm->callback, alarm->opaque ); + + return alarm; +} + +/** + * Delete a timer + * + * @v timer Timer + * @v device Device opaque pointer + */ +static void __asmcall slirp_timer_free ( void *timer, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + struct slirp_alarm *alarm = timer; + + /* Ignore timers that failed to allocate */ + if ( ! alarm ) + return; + + /* Stop timer */ + stop_timer ( &alarm->timer ); + + /* Free timer */ + free ( alarm ); + DBGC ( slirp, "SLIRP %p timer %p freed\n", slirp, alarm ); +} + +/** + * Set timer expiry time + * + * @v timer Timer + * @v expire Expiry time + * @v device Device opaque pointer + */ +static void __asmcall slirp_timer_mod ( void *timer, int64_t expire, + void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + struct slirp_alarm *alarm = timer; + int64_t timeout_ms; + unsigned long timeout; + + /* Ignore timers that failed to allocate */ + if ( ! alarm ) + return; + + /* (Re)start timer */ + timeout_ms = ( expire - ( currticks() / TICKS_PER_MS ) ); + if ( timeout_ms < 0 ) + timeout_ms = 0; + timeout = ( timeout_ms * TICKS_PER_MS ); + start_timer_fixed ( &alarm->timer, timeout ); + DBGC ( slirp, "SLIRP %p timer %p set for %ld ticks\n", + slirp, alarm, timeout ); +} + +/** + * Register file descriptor for polling + * + * @v fd File descriptor + * @v device Device opaque pointer + */ +static void __asmcall slirp_register_poll_fd ( int fd, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + + DBGC ( slirp, "SLIRP %p registered FD %d\n", slirp, fd ); +} + +/** + * Unregister file descriptor + * + * @v fd File descriptor + * @v device Device opaque pointer + */ +static void __asmcall slirp_unregister_poll_fd ( int fd, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + + DBGC ( slirp, "SLIRP %p unregistered FD %d\n", slirp, fd ); +} + +/** + * Notify that new events are ready + * + * @v device Device opaque pointer + */ +static void __asmcall slirp_notify ( void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + + DBGC2 ( slirp, "SLIRP %p notified\n", slirp ); +} + +/** Slirp callbacks */ +static struct slirp_callbacks slirp_callbacks = { + .send_packet = slirp_send_packet, + .guest_error = slirp_guest_error, + .clock_get_ns = slirp_clock_get_ns, + .timer_new = slirp_timer_new, + .timer_free = slirp_timer_free, + .timer_mod = slirp_timer_mod, + .register_poll_fd = slirp_register_poll_fd, + .unregister_poll_fd = slirp_unregister_poll_fd, + .notify = slirp_notify, +}; + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int slirp_open ( struct net_device *netdev ) { + struct slirp_nic *slirp = netdev->priv; + + /* Nothing to do */ + DBGC ( slirp, "SLIRP %p opened\n", slirp ); + + return 0; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void slirp_close ( struct net_device *netdev ) { + struct slirp_nic *slirp = netdev->priv; + + /* Nothing to do */ + DBGC ( slirp, "SLIRP %p closed\n", slirp ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slirp_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct slirp_nic *slirp = netdev->priv; + + /* Transmit packet */ + linux_slirp_input ( slirp->slirp, iobuf->data, iob_len ( iobuf ) ); + netdev_tx_complete ( netdev, iobuf ); + + return 0; +} + +/** + * Add polling file descriptor + * + * @v fd File descriptor + * @v events Events of interest + * @v device Device opaque pointer + * @ret index File descriptor index + */ +static int __asmcall slirp_add_poll ( int fd, int events, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + struct pollfd *pollfd; + unsigned int index; + + /* Fail if too many descriptors are registered */ + if ( slirp->numfds >= SLIRP_MAX_FDS ) { + DBGC ( slirp, "SLIRP %p too many file descriptors\n", slirp ); + return -1; + } + + /* Populate polling file descriptor */ + index = slirp->numfds++; + pollfd = &slirp->pollfds[index]; + pollfd->fd = fd; + pollfd->events = 0; + if ( events & SLIRP_EVENT_IN ) + pollfd->events |= POLLIN; + if ( events & SLIRP_EVENT_OUT ) + pollfd->events |= POLLOUT; + if ( events & SLIRP_EVENT_PRI ) + pollfd->events |= POLLPRI; + if ( events & SLIRP_EVENT_ERR ) + pollfd->events |= POLLERR; + if ( events & SLIRP_EVENT_HUP ) + pollfd->events |= ( POLLHUP | POLLRDHUP ); + DBGCP ( slirp, "SLIRP %p polling FD %d event mask %#04x(%#04x)\n", + slirp, fd, events, pollfd->events ); + + return index; +} + +/** + * Get returned events for a file descriptor + * + * @v index File descriptor index + * @v device Device opaque pointer + * @ret events Returned events + */ +static int __asmcall slirp_get_revents ( int index, void *device ) { + struct net_device *netdev = device; + struct slirp_nic *slirp = netdev->priv; + int revents; + int events; + + /* Ignore failed descriptors */ + if ( index < 0 ) + return 0; + + /* Collect events */ + revents = slirp->pollfds[index].revents; + events = 0; + if ( revents & POLLIN ) + events |= SLIRP_EVENT_IN; + if ( revents & POLLOUT ) + events |= SLIRP_EVENT_OUT; + if ( revents & POLLPRI ) + events |= SLIRP_EVENT_PRI; + if ( revents & POLLERR ) + events |= SLIRP_EVENT_ERR; + if ( revents & ( POLLHUP | POLLRDHUP ) ) + events |= SLIRP_EVENT_HUP; + if ( events ) { + DBGC2 ( slirp, "SLIRP %p polled FD %d events %#04x(%#04x)\n", + slirp, slirp->pollfds[index].fd, events, revents ); + } + + return events; +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void slirp_poll ( struct net_device *netdev ) { + struct slirp_nic *slirp = netdev->priv; + uint32_t timeout = 0; + int ready; + int error; + + /* Rebuild polling file descriptor list */ + slirp->numfds = 0; + linux_slirp_pollfds_fill ( slirp->slirp, &timeout, + slirp_add_poll, netdev ); + + /* Poll descriptors */ + ready = linux_poll ( slirp->pollfds, slirp->numfds, 0 ); + error = ( ready == -1 ); + linux_slirp_pollfds_poll ( slirp->slirp, error, slirp_get_revents, + netdev ); + + /* Record polling errors */ + if ( error ) { + DBGC ( slirp, "SLIRP %p poll failed: %s\n", + slirp, linux_strerror ( linux_errno ) ); + netdev_rx_err ( netdev, NULL, -ELINUX ( linux_errno ) ); + } +} + +/** Network device operations */ +static struct net_device_operations slirp_operations = { + .open = slirp_open, + .close = slirp_close, + .transmit = slirp_transmit, + .poll = slirp_poll, +}; + +/****************************************************************************** + * + * Linux driver interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v linux Linux device + * @v request Device creation request + * @ret rc Return status code + */ +static int slirp_probe ( struct linux_device *linux, + struct linux_device_request *request ) { + struct net_device *netdev; + struct slirp_nic *slirp; + struct slirp_config config; + int rc; + + /* Allocate device */ + netdev = alloc_etherdev ( sizeof ( *slirp ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &slirp_operations ); + linux_set_drvdata ( linux, netdev ); + snprintf ( linux->dev.name, sizeof ( linux->dev.name ), "host" ); + netdev->dev = &linux->dev; + memcpy ( netdev->hw_addr, slirp_default_mac, ETH_ALEN ); + slirp = netdev->priv; + memset ( slirp, 0, sizeof ( *slirp ) ); + + /* Apply requested settings */ + linux_apply_settings ( &request->settings, + netdev_settings ( netdev ) ); + + /* Initialise default configuration (matching qemu) */ + memset ( &config, 0, sizeof ( config ) ); + config.version = 1; + config.in_enabled = true; + config.vnetwork.s_addr = htonl ( 0x0a000200 ); /* 10.0.2.0 */ + config.vnetmask.s_addr = htonl ( 0xffffff00 ); /* 255.255.255.0 */ + config.vhost.s_addr = htonl ( 0x0a000202 ); /* 10.0.2.2 */ + config.in6_enabled = true; + config.vdhcp_start.s_addr = htonl ( 0x0a00020f ); /* 10.0.2.15 */ + config.vnameserver.s_addr = htonl ( 0x0a000203 ); /* 10.0.2.3 */ + + /* Instantiate device */ + slirp->slirp = linux_slirp_new ( &config, &slirp_callbacks, netdev ); + if ( ! slirp->slirp ) { + DBGC ( slirp, "SLIRP could not instantiate\n" ); + rc = -ENODEV; + goto err_new; + } + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + /* Set link up since there is no concept of link state */ + netdev_link_up ( netdev ); + + return 0; + + unregister_netdev ( netdev ); + err_register: + linux_slirp_cleanup ( slirp->slirp ); + err_new: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v linux Linux device + */ +static void slirp_remove ( struct linux_device *linux ) { + struct net_device *netdev = linux_get_drvdata ( linux ); + struct slirp_nic *slirp = netdev->priv; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Shut down device */ + linux_slirp_cleanup ( slirp->slirp ); + + /* Free network device */ + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** Slirp driver */ +struct linux_driver slirp_driver __linux_driver = { + .name = "slirp", + .probe = slirp_probe, + .remove = slirp_remove, + .can_probe = 1, +}; |