/* $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;
}