root/usr.bin/rump_dhcpclient/main.c
/*      $NetBSD: main.c,v 1.4 2015/06/16 22:54:10 christos Exp $        */

/*-
 * Copyright (c) 2011 Antti Kantee.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/if_dl.h>

#include <err.h>
#include <errno.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>

#include <rump/rump_syscalls.h>
#include <rump/rumpclient.h>

#include "configure.h"
#include "dhcp.h"
#include "net.h"

struct interface *ifaces;

__dead static void
usage(void)
{

        fprintf(stderr, "Usage: %s ifname\n", getprogname());
        exit(1);
}

int
get_hwaddr(struct interface *ifp)
{
        struct if_laddrreq iflr;
        struct sockaddr_dl *sdl;
        int s, sverrno;

        memset(&iflr, 0, sizeof(iflr));
        strlcpy(iflr.iflr_name, ifp->name, sizeof(iflr.iflr_name));
        iflr.addr.ss_family = AF_LINK;

        sdl = satosdl(&iflr.addr);
        sdl->sdl_alen = ETHER_ADDR_LEN;

        if ((s = rump_sys_socket(AF_LINK, SOCK_DGRAM, 0)) == -1)
                return -1;

        if (rump_sys_ioctl(s, SIOCGLIFADDR, &iflr) == -1) {
                sverrno = errno;
                rump_sys_close(s);
                errno = sverrno;
                return -1;
        }

        /* XXX: is that the right way to copy the link address? */
        memcpy(ifp->hwaddr, sdl->sdl_data+strlen(ifp->name), ETHER_ADDR_LEN);
        ifp->hwlen = ETHER_ADDR_LEN;
        ifp->family = ARPHRD_ETHER;

        rump_sys_close(s);
        return 0;
}

static void
send_discover(struct interface *ifp)
{
        struct dhcp_message *dhcp;
        uint8_t *udp;
        ssize_t mlen, ulen;
        struct in_addr ia;

        memset(&ia, 0, sizeof(ia));

        mlen = make_message(&dhcp, ifp, DHCP_DISCOVER);
        ulen = make_udp_packet(&udp, (void *)dhcp, mlen, ia, ia);
        if (send_raw_packet(ifp, ETHERTYPE_IP, udp, ulen) == -1)
                err(EXIT_FAILURE, "sending discover failed");
}

static void
send_request(struct interface *ifp)
{
        struct dhcp_message *dhcp;
        uint8_t *udp;
        ssize_t mlen, ulen;
        struct in_addr ia;

        memset(&ia, 0, sizeof(ia));

        mlen = make_message(&dhcp, ifp, DHCP_REQUEST);
        ulen = make_udp_packet(&udp, (void *)dhcp, mlen, ia, ia);
        if (send_raw_packet(ifp, ETHERTYPE_IP, udp, ulen) == -1)
                err(EXIT_FAILURE, "sending discover failed");
}

/* wait for 5s by default */
#define RESPWAIT 5000
static void
get_network(struct interface *ifp, uint8_t **rawp,
        const struct dhcp_message **dhcpp)
{
        struct pollfd pfd;
        const struct dhcp_message *dhcp;
        const uint8_t *data;
        uint8_t *raw;
        ssize_t n;

        pfd.fd = ifp->raw_fd;
        pfd.events = POLLIN;

        raw = xmalloc(udp_dhcp_len);
        for (;;) {
                switch (rump_sys_poll(&pfd, 1, RESPWAIT)) {
                case 0:
                        errx(EXIT_FAILURE, "timed out waiting for response");
                case -1:
                        err(EXIT_FAILURE, "poll failed");
                default:
                        break;
                }

                if ((n = get_raw_packet(ifp, ETHERTYPE_IP,
                    raw, udp_dhcp_len)) < 1)
                        continue;

                if (valid_udp_packet(raw, n, NULL) == -1) {
                        fprintf(stderr, "invalid packet received.  retrying\n");
                        continue;
                }

                n = get_udp_data(&data, raw);
                if ((size_t)n > sizeof(*dhcp)) {
                        fprintf(stderr, "invalid packet size.  retrying\n");
                        continue;
                }
                dhcp = (const void *)data;

                /* XXX: what if packet is too small? */

                /* some sanity checks */
                if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
                        /* ignore */
                        continue;
                }

                if (ifp->state->xid != dhcp->xid) {
                        fprintf(stderr, "invalid transaction.  retrying\n");
                        continue;
                }

                break;
        }

        *rawp = raw;
        *dhcpp = dhcp;
}

static void
get_offer(struct interface *ifp)
{
        const struct dhcp_message *dhcp;
        uint8_t *raw;
        uint8_t type;

        get_network(ifp, &raw, &dhcp);

        get_option_uint8(&type, dhcp, DHO_MESSAGETYPE);
        switch (type) {
        case DHCP_OFFER:
                break;
        case DHCP_NAK:
                errx(EXIT_FAILURE, "got NAK from dhcp server");
        default:
                errx(EXIT_FAILURE, "didn't receive offer");
        }

        ifp->state->offer = xzalloc(sizeof(*ifp->state->offer));
        memcpy(ifp->state->offer, dhcp, sizeof(*ifp->state->offer));
        ifp->state->lease.addr.s_addr = dhcp->yiaddr;
        ifp->state->lease.cookie = dhcp->cookie;
        free(raw);
}

static void
get_ack(struct interface *ifp)
{
        const struct dhcp_message *dhcp;
        uint8_t *raw;
        uint8_t type;

        get_network(ifp, &raw, &dhcp);
        get_option_uint8(&type, dhcp, DHO_MESSAGETYPE);
        if (type != DHCP_ACK)
                errx(EXIT_FAILURE, "didn't receive ack");

        ifp->state->new = ifp->state->offer;
        get_lease(&ifp->state->lease, ifp->state->new);
}

int
main(int argc, char *argv[])
{
        struct interface *ifp;
        struct if_options *ifo;
        const int mib[] = { CTL_KERN, KERN_HOSTNAME };
        size_t hlen;

        setprogname(argv[0]);
        if (argc != 2)
                usage();

        if (rumpclient_init() == -1)
                err(EXIT_FAILURE, "init failed");

        if (init_sockets() == -1)
                err(EXIT_FAILURE, "failed to init sockets");
        if ((ifp = init_interface(argv[1])) == NULL)
                err(EXIT_FAILURE, "cannot init %s", argv[1]);
        ifaces = ifp;
        if (open_socket(ifp, ETHERTYPE_IP) == -1)
                err(EXIT_FAILURE, "bpf");
        up_interface(ifp);

        ifp->state = xzalloc(sizeof(*ifp->state));
        ifp->state->options = ifo = xzalloc(sizeof(*ifp->state->options));
        ifp->state->xid = arc4random();

        hlen = sizeof(ifo->hostname);
        if (rump_sys___sysctl(mib, __arraycount(mib), ifo->hostname, &hlen,
            NULL, 0) == -1)
                snprintf(ifo->hostname, sizeof(ifo->hostname),
                    "unknown.hostname");
        ifo->options = DHCPCD_GATEWAY | DHCPCD_HOSTNAME;

        if (get_hwaddr(ifp) == -1)
                err(EXIT_FAILURE, "failed to get hwaddr for %s", ifp->name);

        send_discover(ifp);
        get_offer(ifp);
        send_request(ifp);
        get_ack(ifp);

        configure(ifp);

        return 0;
}