diff options
Diffstat (limited to '')
-rw-r--r-- | usr/kinit/ipconfig/Kbuild | 35 | ||||
-rw-r--r-- | usr/kinit/ipconfig/README.ipconfig | 120 | ||||
-rw-r--r-- | usr/kinit/ipconfig/bootp_packet.h | 44 | ||||
-rw-r--r-- | usr/kinit/ipconfig/bootp_proto.c | 565 | ||||
-rw-r--r-- | usr/kinit/ipconfig/bootp_proto.h | 10 | ||||
-rw-r--r-- | usr/kinit/ipconfig/dhcp_proto.c | 301 | ||||
-rw-r--r-- | usr/kinit/ipconfig/dhcp_proto.h | 19 | ||||
-rw-r--r-- | usr/kinit/ipconfig/ipconfig.h | 25 | ||||
-rw-r--r-- | usr/kinit/ipconfig/main.c | 924 | ||||
-rw-r--r-- | usr/kinit/ipconfig/netdev.c | 279 | ||||
-rw-r--r-- | usr/kinit/ipconfig/netdev.h | 107 | ||||
-rw-r--r-- | usr/kinit/ipconfig/packet.c | 278 | ||||
-rw-r--r-- | usr/kinit/ipconfig/packet.h | 12 |
13 files changed, 2719 insertions, 0 deletions
diff --git a/usr/kinit/ipconfig/Kbuild b/usr/kinit/ipconfig/Kbuild new file mode 100644 index 0000000..686b03b --- /dev/null +++ b/usr/kinit/ipconfig/Kbuild @@ -0,0 +1,35 @@ +# +# Kbuild file for ipconfig +# + +static-y := static/ipconfig +shared-y := shared/ipconfig + +# common .o files +objs := main.o netdev.o packet.o +# dhcp +objs += dhcp_proto.o +# bootp +objs += bootp_proto.o + + +# TODO - do we want a stripped version +# TODO - do we want the static.g + shared.g directories? + + +# Create built-in.o with all object files (used by kinit) +lib-y := $(objs) + +# .o files used to built executables +static/ipconfig-y := $(objs) +shared/ipconfig-y := $(objs) + +# Cleaning +clean-dirs := static shared + +# install binary +ifdef KLIBCSHAREDFLAGS +install-y := $(shared-y) +else +install-y := $(static-y) +endif diff --git a/usr/kinit/ipconfig/README.ipconfig b/usr/kinit/ipconfig/README.ipconfig new file mode 100644 index 0000000..5ee87e5 --- /dev/null +++ b/usr/kinit/ipconfig/README.ipconfig @@ -0,0 +1,120 @@ +BOOTP/DHCP client for klibc +--------------------------- + +Usage: + +ipconfig [-c proto] [-d interface] [-i identifier] + [-n] [-p port] [-t timeout] [interface ...] + +-c proto Use PROTO as the configuration protocol for all + interfaces, unless overridden by specific interfaces. +-d interface Either the name of an interface, or a long spec. +-i identifier DHCP vendor class identifier. The default is + "Linux ipconfig". +-n Do nothing - just print the configuration that would + be performed. +-p port Send bootp/dhcp broadcasts from PORT, to PORT - 1. +-t timeout Give up on all unconfigured interfaces after TIMEOUT secs. + +You can configure multiple interfaces by passing multiple interface +specs on the command line, or by using the special interface name +"all". If you're autoconfiguring any interfaces, ipconfig will wait +until either all such interfaces have been configured, or the timeout +passes. + +PROTO can be one of the following, which selects the autoconfiguration +protocol to use: + +not specified use all protocols (the default) +dhcp use bootp and dhcp +bootp use bootp only +rarp use rarp (not currently supported) +none no autoconfiguration - either static config, or none at all + +An interface spec can be either short form, which is just the name of +an interface (eth0 or whatever), or long form. The long form consists +of two or more fields, separated by colons: + +<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>: + <dns0-ip>:<dns1-ip>:<ntp0-ip>:... + + <client-ip> IP address of the client. If empty, the address will + either be determined by RARP/BOOTP/DHCP. What protocol + is used de- pends on the <autoconf> parameter. If this + parameter is not empty, autoconf will be used. + + <server-ip> IP address of the NFS server. If RARP is used to + determine the client address and this parameter is NOT + empty only replies from the specified server are + accepted. To use different RARP and NFS server, + specify your RARP server here (or leave it blank), and + specify your NFS server in the `nfsroot' parameter + (see above). If this entry is blank the address of the + server is used which answered the RARP/BOOTP/DHCP + request. + + <gw-ip> IP address of a gateway if the server is on a different + subnet. If this entry is empty no gateway is used and the + server is assumed to be on the local network, unless a + value has been received by BOOTP/DHCP. + + <netmask> Netmask for local network interface. If this is empty, + the netmask is derived from the client IP address assuming + classful addressing, unless overridden in BOOTP/DHCP reply. + + <hostname> Name of the client. If empty, the client IP address is + used in ASCII notation, or the value received by + BOOTP/DHCP. + + <device> Name of network device to use. If this is empty, all + devices are used for RARP/BOOTP/DHCP requests, and the + first one we receive a reply on is configured. If you + have only one device, you can safely leave this blank. + + <autoconf> Method to use for autoconfiguration. If this is either + 'rarp', 'bootp', or 'dhcp' the specified protocol is + used. If the value is 'both', 'all' or empty, all + protocols are used. 'off', 'static' or 'none' means + no autoconfiguration. + + <dns0-ip> IP address of primary nameserver. + + Default: None if not using autoconfiguration; determined + automatically if using autoconfiguration. + + <dns1-ip> IP address of secondary nameserver. + See <dns0-ip>. + + <ntp0-ip> IP address of a Network Time Protocol (NTP) server. + Currently ignored. + + ... Additional fields will be ignored. + +IP addresses and netmasks must be either absent (defaulting to zero) +or presented in dotted-quad notation. + +An interface spec can be prefixed with either "ip=", "nfsaddrs=", both +of which are ignored. These (along with the ugliness of the long +form) are present for compatibility with the in-kernel ipconfig code +from 2.4 and earlier kernels. + +Here are a few examples of valid ipconfig command lines. + +Enable the loopback interface: + ipconfig 127.0.0.1:::::lo:none + +Try to configure eth0 using bootp for up to 30 seconds: + ipconfig -t 30 -c bootp eth0 + +Configure eth0 and eth1 using dhcp or bootp, and eth2 statically: + ipconfig -c any eth0 eth1 192.168.1.1:::::eth2:none + +-- + +From Russell's original README, and still true: + +The code in main.c is yucky imho. Needs cleaning. + +-- +Russell King (2002/10/22) +Bryan O'Sullivan (2003/04/29) diff --git a/usr/kinit/ipconfig/bootp_packet.h b/usr/kinit/ipconfig/bootp_packet.h new file mode 100644 index 0000000..1d5bd0d --- /dev/null +++ b/usr/kinit/ipconfig/bootp_packet.h @@ -0,0 +1,44 @@ +#ifndef BOOTP_PACKET_H +#define BOOTP_PACKET_H + +#include <sys/uio.h> + +struct netdev; + +/* packet ops */ +#define BOOTP_REQUEST 1 +#define BOOTP_REPLY 2 + +/* your basic bootp packet */ +struct bootp_hdr { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + char server_name[64]; + char boot_file[128]; + /* 312 bytes of extensions */ +}; + +/* + * memory size of BOOTP Vendor Extensions/DHCP Options for receiving + * + * generic_ether_mtu:1500, min_sizeof(ip_hdr):20, sizeof(udp_hdr):8 + * + * #define BOOTP_EXTS_SIZE (1500 - 20 - 8 - sizeof(struct bootp_hdr)) + */ +/* larger size for backward compatibility of ipconfig */ +#define BOOTP_EXTS_SIZE 1500 + +/* minimum length of BOOTP/DHCP packet on sending */ +#define BOOTP_MIN_LEN 300 + +#endif /* BOOTP_PACKET_H */ diff --git a/usr/kinit/ipconfig/bootp_proto.c b/usr/kinit/ipconfig/bootp_proto.c new file mode 100644 index 0000000..f6f9dd4 --- /dev/null +++ b/usr/kinit/ipconfig/bootp_proto.c @@ -0,0 +1,565 @@ +/* + * BOOTP packet protocol handling. + */ +#include <sys/types.h> +#include <sys/uio.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <netinet/in.h> + +#include "ipconfig.h" +#include "netdev.h" +#include "bootp_packet.h" +#include "bootp_proto.h" +#include "packet.h" + +static uint8_t bootp_options[312] = { + [ 0] = 99, 130, 83, 99,/* RFC1048 magic cookie */ + [ 4] = 1, 4, /* 4- 9 subnet mask */ + [ 10] = 3, 4, /* 10- 15 default gateway */ + [ 16] = 5, 8, /* 16- 25 nameserver */ + [ 26] = 12, 32, /* 26- 59 host name */ + [ 60] = 40, 32, /* 60- 95 nis domain name */ + [ 96] = 17, 40, /* 96-137 boot path */ + [138] = 57, 2, 1, 150, /* 138-141 extension buffer */ + [142] = 255, /* end of list */ +}; + +/* + * Send a plain bootp request packet with options + */ +int bootp_send_request(struct netdev *dev) +{ + struct bootp_hdr bootp; + struct iovec iov[] = { + /* [0] = ip + udp headers */ + [1] = {&bootp, sizeof(bootp)}, + [2] = {bootp_options, 312} + }; + + memset(&bootp, 0, sizeof(struct bootp_hdr)); + + bootp.op = BOOTP_REQUEST, bootp.htype = dev->hwtype; + bootp.hlen = dev->hwlen; + bootp.xid = dev->bootp.xid; + bootp.ciaddr = dev->ip_addr; + bootp.secs = htons(time(NULL) - dev->open_time); + memcpy(bootp.chaddr, dev->hwaddr, 16); + + dprintf("-> bootp xid 0x%08x secs 0x%08x ", + bootp.xid, ntohs(bootp.secs)); + + return packet_send(dev, iov, 2); +} + +/* + * DESCRIPTION + * bootp_ext119_decode() decodes Domain Search Option data. + * The decoded string is separated with ' '. + * For example, it is either "foo.bar.baz. bar.baz.", "foo.bar.", or "foo.". + * + * ARGUMENTS + * const uint8_t *ext + * *ext is a pointer to a DHCP Domain Search Option data. *ext does not + * include a tag(code) octet and a length octet in DHCP options. + * For example, if *ext is {3, 'f', 'o', 'o', 0}, this function returns + * a pointer to a "foo." string. + * + * int16_t ext_size + * ext_size is the memory size of *ext. For example, + * if *ext is {3, 'f', 'o', 'o', 0}, ext_size must be 5. + * + * uint8_t *tmp + * *tmp is a pointer to a temporary memory space for decoding. + * The memory size must be equal to or more than ext_size. + * 'memset(tmp, 0, sizeof(tmp));' is not required, but values in *tmp + * are changed in decoding process. + * + * RETURN VALUE + * if OK, a pointer to a decoded string malloc-ed + * else , NULL + * + * SEE ALSO RFC3397 + */ +static char *bootp_ext119_decode(const void *ext, int16_t ext_size, void *tmp) +{ + uint8_t *u8ext; + int_fast32_t i; + int_fast32_t decoded_size; + int_fast8_t currentdomain_is_singledot; + + /* only for validating *ext */ + uint8_t *is_pointee; + int_fast32_t is_pointee_size; + + /* only for structing a decoded string */ + char *decoded_str; + int_fast32_t dst_i; + + if (ext == NULL || ext_size <= 0 || tmp == NULL) + return NULL; + + u8ext = (uint8_t *)ext; + is_pointee = tmp; + memset(is_pointee, 0, (size_t)ext_size); + is_pointee_size = 0; + + /* + * validate the format of *ext and + * calculate the memory size for a decoded string + */ + i = 0; + decoded_size = 0; + currentdomain_is_singledot = 1; + while (1) { + if (i >= ext_size) + return NULL; + + if (u8ext[i] == 0) { + /* Zero-ending */ + if (currentdomain_is_singledot) + decoded_size++; /* for '.' */ + decoded_size++; /* for ' ' or '\0' */ + currentdomain_is_singledot = 1; + i++; + if (i == ext_size) + break; + is_pointee_size = i; + } else if (u8ext[i] < 0x40) { + /* Label(sub-domain string) */ + int j; + + /* loosely validate characters for domain names */ + if (i + u8ext[i] >= ext_size) + return NULL; + for (j = i + 1; j <= i + u8ext[i]; j++) + if (!(u8ext[j] == '-' || + ('0' <= u8ext[j] && u8ext[j] <= '9') || + ('A' <= u8ext[j] && u8ext[j] <= 'Z') || + ('a' <= u8ext[j] && u8ext[j] <= 'z'))) + return NULL; + + is_pointee[i] = 1; + decoded_size += u8ext[i] + 1; /* for Label + '.' */ + currentdomain_is_singledot = 0; + i += u8ext[i] + 1; + } else if (u8ext[i] < 0xc0) + return NULL; + + else { + /* Compression-pointer (to a prior Label) */ + int_fast32_t p; + + if (i + 1 >= ext_size) + return NULL; + + p = ((0x3f & u8ext[i]) << 8) + u8ext[i + 1]; + if (!(p < is_pointee_size && is_pointee[p])) + return NULL; + + while (1) { + /* u8ext[p] was validated */ + if (u8ext[p] == 0) { + /* Zero-ending */ + decoded_size++; + break; + } else if (u8ext[p] < 0x40) { + /* Label(sub-domain string) */ + decoded_size += u8ext[p] + 1; + p += u8ext[p] + 1; + } else { + /* Compression-pointer */ + p = ((0x3f & u8ext[p]) << 8) + + u8ext[p + 1]; + } + } + + currentdomain_is_singledot = 1; + i += 2; + if (i == ext_size) + break; + is_pointee_size = i; + } + } + + + /* + * construct a decoded string + */ + decoded_str = malloc(decoded_size); + if (decoded_str == NULL) + return NULL; + + i = 0; + dst_i = 0; + currentdomain_is_singledot = 1; + while (1) { + if (u8ext[i] == 0) { + /* Zero-ending */ + if (currentdomain_is_singledot) { + if (dst_i != 0) + dst_i++; + decoded_str[dst_i] = '.'; + } + dst_i++; + decoded_str[dst_i] = ' '; + + currentdomain_is_singledot = 1; + i++; + if (i == ext_size) + break; + } else if (u8ext[i] < 0x40) { + /* Label(sub-domain string) */ + if (dst_i != 0) + dst_i++; + memcpy(&decoded_str[dst_i], &u8ext[i + 1], + (size_t)u8ext[i]); + dst_i += u8ext[i]; + decoded_str[dst_i] = '.'; + + currentdomain_is_singledot = 0; + i += u8ext[i] + 1; + } else { + /* Compression-pointer (to a prior Label) */ + int_fast32_t p; + + p = ((0x3f & u8ext[i]) << 8) + u8ext[i + 1]; + while (1) { + if (u8ext[p] == 0) { + /* Zero-ending */ + decoded_str[dst_i++] = '.'; + decoded_str[dst_i] = ' '; + break; + } else if (u8ext[p] < 0x40) { + /* Label(sub-domain string) */ + dst_i++; + memcpy(&decoded_str[dst_i], + &u8ext[p + 1], + (size_t)u8ext[p]); + dst_i += u8ext[p]; + decoded_str[dst_i] = '.'; + + p += u8ext[p] + 1; + } else { + /* Compression-pointer */ + p = ((0x3f & u8ext[p]) << 8) + + u8ext[p + 1]; + } + } + + currentdomain_is_singledot = 1; + i += 2; + if (i == ext_size) + break; + } + } + decoded_str[dst_i] = '\0'; +#ifdef DEBUG + if (dst_i + 1 != decoded_size) { + dprintf("bug:%s():bottom: malloc(%ld), write(%ld)\n", + __func__, (long)decoded_size, (long)(dst_i + 1)); + exit(1); + } +#endif + return decoded_str; +} + +/* + * DESCRIPTION + * bootp_ext121_decode() decodes Classless Route Option data. + * + * ARGUMENTS + * const uint8_t *ext + * *ext is a pointer to a DHCP Classless Route Option data. + * For example, if *ext is {16, 192, 168, 192, 168, 42, 1}, + * this function returns a pointer to + * { + * subnet = 192.168.0.0; + * netmask_width = 16; + * gateway = 192.168.42.1; + * next = NULL; + * } + * + * int16_t ext_size + * ext_size is the memory size of *ext. For example, + * if *ext is {16, 192, 168, 192, 168, 42, 1}, ext_size must be 7. + * + * RETURN VALUE + * if OK, a pointer to a decoded struct route malloc-ed + * else , NULL + * + * SEE ALSO RFC3442 + */ +struct route *bootp_ext121_decode(const uint8_t *ext, int16_t ext_size) +{ + int16_t index = 0; + uint8_t netmask_width; + uint8_t significant_octets; + struct route *routes = NULL; + struct route *prev_route = NULL; + + while (index < ext_size) { + netmask_width = ext[index]; + index++; + if (netmask_width > 32) { + printf("IP-Config: Given Classless Route Option subnet mask width '%u' " + "exceeds IPv4 limit of 32. Ignoring remaining option.\n", + netmask_width); + return routes; + } + significant_octets = netmask_width / 8 + (netmask_width % 8 > 0); + if (ext_size - index < significant_octets + 4) { + printf("IP-Config: Given Classless Route Option remaining lengths (%u octets) " + "is shorter than the expected %u octets. Ignoring remaining options.\n", + ext_size - index, significant_octets + 4); + return routes; + } + + struct route *route = malloc(sizeof(struct route)); + if (route == NULL) + return routes; + + /* convert only significant octets from byte array into integer in network byte order */ + route->subnet = 0; + memcpy(&route->subnet, &ext[index], significant_octets); + index += significant_octets; + /* RFC3442 demands: After deriving a subnet number and subnet mask from + each destination descriptor, the DHCP client MUST zero any bits in + the subnet number where the corresponding bit in the mask is zero. */ + route->subnet &= netdev_genmask(netmask_width); + + /* convert octet array into network byte order */ + memcpy(&route->gateway, &ext[index], 4); + index += 4; + + route->netmask_width = netmask_width; + route->next = NULL; + + if (prev_route == NULL) { + routes = route; + } else { + prev_route->next = route; + } + prev_route = route; + } + return routes; +} + +/* + * Parse a bootp reply packet + */ +int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr, + uint8_t *exts, int extlen) +{ + uint8_t ext119_buf[BOOTP_EXTS_SIZE]; + int16_t ext119_len = 0; + uint8_t ext121_buf[BOOTP_EXTS_SIZE]; + int16_t ext121_len = 0; + + dev->bootp.gateway = hdr->giaddr; + dev->ip_addr = hdr->yiaddr; + dev->ip_server = hdr->siaddr; + dev->ip_netmask = INADDR_ANY; + dev->ip_broadcast = INADDR_ANY; + dev->ip_gateway = hdr->giaddr; + dev->ip_nameserver[0] = INADDR_ANY; + dev->ip_nameserver[1] = INADDR_ANY; + dev->hostname[0] = '\0'; + dev->nisdomainname[0] = '\0'; + dev->bootpath[0] = '\0'; + memcpy(&dev->filename, &hdr->boot_file, FNLEN); + + if (extlen >= 4 && exts[0] == 99 && exts[1] == 130 && + exts[2] == 83 && exts[3] == 99) { + uint8_t *ext; + + for (ext = exts + 4; ext - exts < extlen;) { + int len; + uint8_t opt = *ext++; + + if (opt == 0) + continue; + else if (opt == 255) + break; + + if (ext - exts >= extlen) + break; + len = *ext++; + + if (ext - exts + len > extlen) + break; + switch (opt) { + case 1: /* subnet mask */ + if (len == 4) + memcpy(&dev->ip_netmask, ext, 4); + break; + case 3: /* default gateway */ + if (len >= 4) + memcpy(&dev->ip_gateway, ext, 4); + break; + case 6: /* DNS server */ + if (len >= 4) + memcpy(&dev->ip_nameserver, ext, + len >= 8 ? 8 : 4); + break; + case 12: /* host name */ + if (len > sizeof(dev->hostname) - 1) + len = sizeof(dev->hostname) - 1; + memcpy(&dev->hostname, ext, len); + dev->hostname[len] = '\0'; + break; + case 15: /* domain name */ + if (len > sizeof(dev->dnsdomainname) - 1) + len = sizeof(dev->dnsdomainname) - 1; + memcpy(&dev->dnsdomainname, ext, len); + dev->dnsdomainname[len] = '\0'; + break; + case 17: /* root path */ + if (len > sizeof(dev->bootpath) - 1) + len = sizeof(dev->bootpath) - 1; + memcpy(&dev->bootpath, ext, len); + dev->bootpath[len] = '\0'; + break; + case 26: /* interface MTU */ + if (len == 2) + dev->mtu = (ext[0] << 8) + ext[1]; + break; + case 28: /* broadcast addr */ + if (len == 4) + memcpy(&dev->ip_broadcast, ext, 4); + break; + case 40: /* NIS domain name */ + if (len > sizeof(dev->nisdomainname) - 1) + len = sizeof(dev->nisdomainname) - 1; + memcpy(&dev->nisdomainname, ext, len); + dev->nisdomainname[len] = '\0'; + break; + case 54: /* server identifier */ + if (len == 4 && !dev->ip_server) + memcpy(&dev->ip_server, ext, 4); + break; + case 119: /* Domain Search Option */ + if (ext119_len >= 0 && + ext119_len + len <= sizeof(ext119_buf)) { + memcpy(ext119_buf + ext119_len, + ext, len); + ext119_len += len; + } else + ext119_len = -1; + + break; + case 121: /* Classless Static Route Option (RFC3442) */ + if (ext121_len >= 0 && + ext121_len + len <= sizeof(ext121_buf)) { + memcpy(ext121_buf + ext121_len, + ext, len); + ext121_len += len; + } else + ext121_len = -1; + + break; + } + + ext += len; + } + } + if (ext119_len > 0) { + char *ret; + uint8_t ext119_tmp[BOOTP_EXTS_SIZE]; + + ret = bootp_ext119_decode(ext119_buf, ext119_len, ext119_tmp); + if (ret != NULL) { + if (dev->domainsearch != NULL) + free(dev->domainsearch); + dev->domainsearch = ret; + } + } + + if (ext121_len > 0) { + struct route *ret; + + ret = bootp_ext121_decode(ext121_buf, ext121_len); + if (ret != NULL) { + struct route *cur = dev->routes; + struct route *next; + while (cur != NULL) { + next = cur->next; + free(cur); + cur = next; + } + dev->routes = ret; + } + } + + /* + * Got packet. + */ + return 1; +} + +/* + * Receive a bootp reply and parse packet + * Returns: + *-1 = Error in packet_recv, try again later + * 0 = Unexpected packet, discarded + * 1 = Correctly received and parsed packet + */ +int bootp_recv_reply(struct netdev *dev) +{ + struct bootp_hdr bootp; + uint8_t bootp_options[BOOTP_EXTS_SIZE]; + struct iovec iov[] = { + /* [0] = ip + udp headers */ + [1] = {&bootp, sizeof(struct bootp_hdr)}, + [2] = {bootp_options, sizeof(bootp_options)} + }; + int ret; + + ret = packet_recv(dev, iov, 3); + if (ret <= 0) + return ret; + + if (ret < sizeof(struct bootp_hdr) || + bootp.op != BOOTP_REPLY || /* RFC951 7.5 */ + bootp.xid != dev->bootp.xid || + memcmp(bootp.chaddr, dev->hwaddr, 16)) + return 0; + + ret -= sizeof(struct bootp_hdr); + + return bootp_parse(dev, &bootp, bootp_options, ret); +} + +/* + * Initialise interface for bootp. + */ +int bootp_init_if(struct netdev *dev) +{ + short flags; + + /* + * Get the device flags + */ + if (netdev_getflags(dev, &flags)) + return -1; + + /* + * We can't do DHCP nor BOOTP if this device + * doesn't support broadcast. + */ + if (dev->mtu < 364 || (flags & IFF_BROADCAST) == 0) { + dev->caps &= ~(CAP_BOOTP | CAP_DHCP); + return 0; + } + + /* + * Get a random XID + */ + dev->bootp.xid = (uint32_t) lrand48(); + dev->open_time = time(NULL); + + return 0; +} diff --git a/usr/kinit/ipconfig/bootp_proto.h b/usr/kinit/ipconfig/bootp_proto.h new file mode 100644 index 0000000..60873ce --- /dev/null +++ b/usr/kinit/ipconfig/bootp_proto.h @@ -0,0 +1,10 @@ +#ifndef IPCONFIG_BOOTP_PROTO_H +#define IPCONFIG_BOOTP_PROTO_H + +int bootp_send_request(struct netdev *dev); +int bootp_recv_reply(struct netdev *dev); +int bootp_parse(struct netdev *dev, struct bootp_hdr *hdr, uint8_t * exts, + int extlen); +int bootp_init_if(struct netdev *dev); + +#endif /* IPCONFIG_BOOTP_PROTO_H */ diff --git a/usr/kinit/ipconfig/dhcp_proto.c b/usr/kinit/ipconfig/dhcp_proto.c new file mode 100644 index 0000000..4e560b8 --- /dev/null +++ b/usr/kinit/ipconfig/dhcp_proto.c @@ -0,0 +1,301 @@ +/* + * DHCP RFC 2131 and 2132 + */ +#include <sys/types.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "ipconfig.h" +#include "netdev.h" +#include "bootp_packet.h" +#include "bootp_proto.h" +#include "dhcp_proto.h" +#include "packet.h" + +static uint8_t dhcp_params[] = { + 1, /* subnet mask */ + 3, /* default gateway */ + 6, /* DNS server */ + 12, /* host name */ + 15, /* domain name */ + 17, /* root path */ + 26, /* interface mtu */ + 28, /* broadcast addr */ + 40, /* NIS domain name (why?) */ + 119, /* Domain Search Option */ + 121, /* Classless Static Route Option (RFC3442) */ +}; + +static uint8_t dhcp_discover_hdr[] = { + 99, 130, 83, 99, /* bootp cookie */ + 53, 1, DHCPDISCOVER, /* dhcp message type */ + 55, sizeof(dhcp_params), /* parameter list */ +}; + +static uint8_t dhcp_request_hdr[] = { + 99, 130, 83, 99, /* boot cookie */ + 53, 1, DHCPREQUEST, /* dhcp message type */ +#define SERVER_IP_OFF 9 + 54, 4, 0, 0, 0, 0, /* server IP */ +#define REQ_IP_OFF 15 + 50, 4, 0, 0, 0, 0, /* requested IP address */ + 55, sizeof(dhcp_params), /* parameter list */ +}; + +static uint8_t dhcp_end[] = { + 255, +}; + +/* Both iovecs below have to have the same structure, since dhcp_send() + pokes at the internals */ +#define DHCP_IOV_LEN 8 + +static struct iovec dhcp_discover_iov[DHCP_IOV_LEN] = { + /* [0] = ip + udp header */ + /* [1] = bootp header */ + [2] = {dhcp_discover_hdr, sizeof(dhcp_discover_hdr)}, + [3] = {dhcp_params, sizeof(dhcp_params)}, + /* [4] = optional vendor class */ + /* [5] = optional hostname */ + /* [6] = {dhcp_end, sizeof(dhcp_end)} */ + /* [7] = optional padding */ +}; + +static struct iovec dhcp_request_iov[DHCP_IOV_LEN] = { + /* [0] = ip + udp header */ + /* [1] = bootp header */ + [2] = {dhcp_request_hdr, sizeof(dhcp_request_hdr)}, + [3] = {dhcp_params, sizeof(dhcp_params)}, + /* [4] = optional vendor class */ + /* [5] = optional hostname */ + /* [6] = {dhcp_end, sizeof(dhcp_end)} */ + /* [7] = optional padding */ +}; + +/* + * Parse a DHCP response packet + * Returns: + * 0 = Unexpected packet, not parsed + * 2 = DHCPOFFER (from dhcp_proto.h) + * 5 = DHCPACK + * 6 = DHCPNACK + */ +static int dhcp_parse(struct netdev *dev, struct bootp_hdr *hdr, + uint8_t *exts, int extlen) +{ + uint8_t type = 0; + uint32_t serverid = INADDR_NONE; + uint32_t leasetime = 0; + int ret = 0; + + if (extlen >= 4 && exts[0] == 99 && exts[1] == 130 && + exts[2] == 83 && exts[3] == 99) { + uint8_t *ext; + + for (ext = exts + 4; ext - exts < extlen;) { + int len; + uint8_t opt = *ext++; + + if (opt == 0) + continue; + else if (opt == 255) + break; + + if (ext - exts >= extlen) + break; + len = *ext++; + + if (ext - exts + len > extlen) + break; + switch (opt) { + case 51: /* IP Address Lease Time */ + if (len == 4) + leasetime = ntohl(*(uint32_t *)ext); + break; + case 53: /* DHCP Message Type */ + if (len == 1) + type = *ext; + break; + case 54: /* Server Identifier */ + if (len == 4) + memcpy(&serverid, ext, 4); + break; + } + ext += len; + } + } + + switch (type) { + case DHCPOFFER: + ret = bootp_parse(dev, hdr, exts, extlen) ? DHCPOFFER : 0; + if (ret == DHCPOFFER && serverid != INADDR_NONE) + dev->serverid = serverid; + dprintf("\n dhcp offer\n"); + break; + + case DHCPACK: + dev->dhcpleasetime = leasetime; + ret = bootp_parse(dev, hdr, exts, extlen) ? DHCPACK : 0; + dprintf("\n dhcp ack\n"); + break; + + case DHCPNAK: + ret = DHCPNAK; + dprintf("\n dhcp nak\n"); + break; + } + return ret; +} + +/* + * Receive and parse a DHCP packet + * Returns: + *-1 = Error in packet_recv, try again later + * 0 = Unexpected packet, discarded + * 2 = DHCPOFFER (from dhcp_proto.h) + * 5 = DHCPACK + * 6 = DHCPNACK + */ +static int dhcp_recv(struct netdev *dev) +{ + struct bootp_hdr bootp; + uint8_t dhcp_options[BOOTP_EXTS_SIZE]; + struct iovec iov[] = { + /* [0] = ip + udp header */ + [1] = {&bootp, sizeof(struct bootp_hdr)}, + [2] = {dhcp_options, sizeof(dhcp_options)} + }; + int ret; + + ret = packet_recv(dev, iov, 3); + if (ret <= 0) + return ret; + + dprintf("\n dhcp xid %08x ", dev->bootp.xid); + + if (ret < sizeof(struct bootp_hdr) || bootp.op != BOOTP_REPLY || + /* RFC951 7.5 */ bootp.xid != dev->bootp.xid || + memcmp(bootp.chaddr, dev->hwaddr, 16)) + return 0; + + ret -= sizeof(struct bootp_hdr); + + return dhcp_parse(dev, &bootp, dhcp_options, ret); +} + +static int dhcp_send(struct netdev *dev, struct iovec *vec) +{ + struct bootp_hdr bootp; + char dhcp_hostname[SYS_NMLN+2]; + uint8_t padding[BOOTP_MIN_LEN - sizeof(struct bootp_hdr)]; + int padding_len; + int i = 4; + int j; + + memset(&bootp, 0, sizeof(struct bootp_hdr)); + + bootp.op = BOOTP_REQUEST; + bootp.htype = dev->hwtype; + bootp.hlen = dev->hwlen; + bootp.xid = dev->bootp.xid; + bootp.ciaddr = INADDR_ANY; + /* yiaddr should always be set to 0 for the messages we're likely + * to send as a DHCP client: DHCPDISCOVER, DHCPREQUEST, DHCPDECLINE, + * DHCPINFORM, DHCPRELEASE + * cf. RFC2131 section 4.1.1, table 5. + */ + bootp.yiaddr = INADDR_ANY; + bootp.giaddr = INADDR_ANY; + bootp.flags = htons(0x8000); + bootp.secs = htons(time(NULL) - dev->open_time); + memcpy(bootp.chaddr, dev->hwaddr, 16); + + vec[1].iov_base = &bootp; + vec[1].iov_len = sizeof(struct bootp_hdr); + + dprintf("xid %08x secs %d ", bootp.xid, ntohs(bootp.secs)); + + if (vendor_class_identifier_len > 2) { + vec[i].iov_base = vendor_class_identifier; + vec[i].iov_len = vendor_class_identifier_len; + i++; + + dprintf("vendor_class_identifier \"%.*s\" ", + vendor_class_identifier_len-2, + vendor_class_identifier+2); + } + + if (dev->reqhostname[0] != '\0') { + int len = strlen(dev->reqhostname); + dhcp_hostname[0] = 12; + dhcp_hostname[1] = len; + memcpy(dhcp_hostname+2, dev->reqhostname, len); + + vec[i].iov_base = dhcp_hostname; + vec[i].iov_len = len+2; + i++; + + printf("hostname %.*s ", len, dhcp_hostname+2); + } + + vec[i].iov_base = dhcp_end; + vec[i].iov_len = sizeof(dhcp_end); + + /* Append padding if DHCP packet length is shorter than BOOTP_MIN_LEN */ + padding_len = sizeof(padding); + for (j = 2; j <= i; j++) + padding_len -= vec[j].iov_len; + if (padding_len > 0) { + memset(padding, 0, padding_len); + i++; + vec[i].iov_base = padding; + vec[i].iov_len = padding_len; + } + + return packet_send(dev, vec, i + 1); +} + +/* + * Send a DHCP discover packet + */ +int dhcp_send_discover(struct netdev *dev) +{ + dev->ip_addr = INADDR_ANY; + dev->ip_gateway = INADDR_ANY; + + dprintf("-> dhcp discover "); + + return dhcp_send(dev, dhcp_discover_iov); +} + +/* + * Receive a DHCP offer packet + */ +int dhcp_recv_offer(struct netdev *dev) +{ + return dhcp_recv(dev); +} + +/* + * Send a DHCP request packet + */ +int dhcp_send_request(struct netdev *dev) +{ + memcpy(&dhcp_request_hdr[SERVER_IP_OFF], &dev->serverid, 4); + memcpy(&dhcp_request_hdr[REQ_IP_OFF], &dev->ip_addr, 4); + + dprintf("-> dhcp request "); + + return dhcp_send(dev, dhcp_request_iov); +} + +/* + * Receive a DHCP ack packet + */ +int dhcp_recv_ack(struct netdev *dev) +{ + return dhcp_recv(dev); +} diff --git a/usr/kinit/ipconfig/dhcp_proto.h b/usr/kinit/ipconfig/dhcp_proto.h new file mode 100644 index 0000000..0fba92f --- /dev/null +++ b/usr/kinit/ipconfig/dhcp_proto.h @@ -0,0 +1,19 @@ +#ifndef IPCONFIG_DHCP_PROTO_H +#define IPCONFIG_DHCP_PROTO_H + +/* DHCP message types */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +int dhcp_send_discover(struct netdev *dev); +int dhcp_recv_offer(struct netdev *dev); +int dhcp_send_request(struct netdev *dev); +int dhcp_recv_ack(struct netdev *dev); + +#endif /* IPCONFIG_DHCP_PROTO_H */ diff --git a/usr/kinit/ipconfig/ipconfig.h b/usr/kinit/ipconfig/ipconfig.h new file mode 100644 index 0000000..d1d7e42 --- /dev/null +++ b/usr/kinit/ipconfig/ipconfig.h @@ -0,0 +1,25 @@ +#ifndef IPCONFIG_IPCONFIG_H +#define IPCONFIG_IPCONFIG_H + +#include <stdint.h> +#include <sys/types.h> + +#define LOCAL_PORT 68 +#define REMOTE_PORT (LOCAL_PORT - 1) + +extern uint16_t cfg_local_port; +extern uint16_t cfg_remote_port; + +extern char vendor_class_identifier[]; +extern int vendor_class_identifier_len; + +int ipconfig_main(int argc, char *argv[]); +uint32_t ipconfig_server_address(void *next); + +#ifdef DEBUG +# define dprintf printf +#else +# define dprintf(...) ((void)0) +#endif + +#endif /* IPCONFIG_IPCONFIG_H */ diff --git a/usr/kinit/ipconfig/main.c b/usr/kinit/ipconfig/main.c new file mode 100644 index 0000000..64c5398 --- /dev/null +++ b/usr/kinit/ipconfig/main.c @@ -0,0 +1,924 @@ +#include <poll.h> +#include <limits.h> +#include <setjmp.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/sysinfo.h> +#include <dirent.h> +#include <fcntl.h> +#include <unistd.h> /* for getopts */ + +#include <net/if_arp.h> + +#include "ipconfig.h" +#include "netdev.h" +#include "bootp_packet.h" +#include "bootp_proto.h" +#include "dhcp_proto.h" +#include "packet.h" + +static const char sysfs_class_net[] = "/sys/class/net"; +static const char *progname; +static jmp_buf abort_buf; +static char do_not_config; +static unsigned int default_caps = CAP_DHCP | CAP_BOOTP | CAP_RARP; +static int loop_timeout = -1; +static int configured; +static int bringup_first = 0; +static int n_devices = 0; + +/* DHCP vendor class identifier */ +char vendor_class_identifier[260]; +int vendor_class_identifier_len; + +struct state { + int state; + int restart_state; + time_t expire; + int retry_period; + + struct netdev *dev; + struct state *next; +}; + +/* #define PROTO_x : for uint8_t proto of struct netdev */ +struct protoinfo { + char *name; +} protoinfos[] = { +#define PROTO_NONE 0 + {"none"}, +#define PROTO_BOOTP 1 + {"bootp"}, +#define PROTO_DHCP 2 + {"dhcp"}, +#define PROTO_RARP 3 + {"rarp"} +}; + +static inline const char *my_inet_ntoa(uint32_t addr) +{ + struct in_addr a; + + a.s_addr = addr; + + return inet_ntoa(a); +} + +static void print_device_config(struct netdev *dev) +{ + int dns0_spaces; + int dns1_spaces; + printf("IP-Config: %s complete", dev->name); + if (dev->proto == PROTO_BOOTP || dev->proto == PROTO_DHCP) + printf(" (%s from %s)", protoinfos[dev->proto].name, + my_inet_ntoa(dev->serverid ? + dev->serverid : dev->ip_server)); + + printf(":\n address: %-16s ", my_inet_ntoa(dev->ip_addr)); + printf("broadcast: %-16s ", my_inet_ntoa(dev->ip_broadcast)); + printf("netmask: %-16s\n", my_inet_ntoa(dev->ip_netmask)); + if (dev->routes != NULL) { + struct route *cur; + char *delim = ""; + printf(" routes :"); + for (cur = dev->routes; cur != NULL; cur = cur->next) { + printf("%s %s/%u", delim, my_inet_ntoa(cur->subnet), cur->netmask_width); + if (cur->gateway != 0) { + printf(" via %s", my_inet_ntoa(cur->gateway)); + } + delim = ","; + } + printf("\n"); + dns0_spaces = 3; + dns1_spaces = 5; + } else { + printf(" gateway: %-16s", my_inet_ntoa(dev->ip_gateway)); + dns0_spaces = 5; + dns1_spaces = 3; + } + printf(" dns0%*c: %-16s", dns0_spaces, ' ', my_inet_ntoa(dev->ip_nameserver[0])); + printf(" dns1%*c: %-16s\n", dns1_spaces, ' ', my_inet_ntoa(dev->ip_nameserver[1])); + if (dev->hostname[0]) + printf(" host : %-64s\n", dev->hostname); + if (dev->dnsdomainname[0]) + printf(" domain : %-64s\n", dev->dnsdomainname); + if (dev->nisdomainname[0]) + printf(" nisdomain: %-64s\n", dev->nisdomainname); + printf(" rootserver: %s ", my_inet_ntoa(dev->ip_server)); + printf("rootpath: %s\n", dev->bootpath); + printf(" filename : %s\n", dev->filename); +} + +static void configure_device(struct netdev *dev) +{ + if (do_not_config) + return; + + if (netdev_setmtu(dev)) + printf("IP-Config: failed to set MTU on %s to %u\n", + dev->name, dev->mtu); + + if (netdev_setaddress(dev)) + printf("IP-Config: failed to set addresses on %s\n", + dev->name); + if (netdev_setroutes(dev)) + printf("IP-Config: failed to set routes on %s\n", + dev->name); + if (dev->hostname[0] && + sethostname(dev->hostname, strlen(dev->hostname))) + printf("IP-Config: failed to set hostname '%s' from %s\n", + dev->hostname, dev->name); +} + +/* + * Escape shell varialbes in git style: + * Always start with a single quote ('), then leave all characters + * except ' and ! unchanged. + */ +static void write_option(FILE *f, const char *name, const char *chr) +{ + + fprintf(f, "%s='", name); + while (*chr) { + switch (*chr) { + case '!': + case '\'': + fprintf(f, "'\\%c'", *chr); + break; + default: + fprintf(f, "%c", *chr); + break; + } + ++chr; + } + fprintf(f, "'\n"); +} + +static void dump_device_config(struct netdev *dev) +{ + char fn[40]; + FILE *f; + /* + * char UINT64_MAX[] = "18446744073709551615"; + * sizeof(UINT64_MAX)==21 + */ + char buf21[21]; + const char path[] = "/run/"; + + snprintf(fn, sizeof(fn), "%snet-%s.conf", path, dev->name); + f = fopen(fn, "w"); + if (f) { + write_option(f, "DEVICE", dev->name); + write_option(f, "PROTO", protoinfos[dev->proto].name); + write_option(f, "IPV4ADDR", + my_inet_ntoa(dev->ip_addr)); + write_option(f, "IPV4BROADCAST", + my_inet_ntoa(dev->ip_broadcast)); + write_option(f, "IPV4NETMASK", + my_inet_ntoa(dev->ip_netmask)); + if (dev->routes != NULL) { + /* Use 6 digits to encode the index */ + char key[23]; + char value[19]; + int i = 0; + struct route *cur; + for (cur = dev->routes; cur != NULL; cur = cur->next) { + snprintf(key, sizeof(key), "IPV4ROUTE%iSUBNET", i); + snprintf(value, sizeof(value), "%s/%u", my_inet_ntoa(cur->subnet), cur->netmask_width); + write_option(f, key, value); + snprintf(key, sizeof(key), "IPV4ROUTE%iGATEWAY", i); + write_option(f, key, my_inet_ntoa(cur->gateway)); + i++; + } + } else { + write_option(f, "IPV4GATEWAY", + my_inet_ntoa(dev->ip_gateway)); + } + write_option(f, "IPV4DNS0", + my_inet_ntoa(dev->ip_nameserver[0])); + write_option(f, "IPV4DNS1", + my_inet_ntoa(dev->ip_nameserver[1])); + write_option(f, "HOSTNAME", dev->hostname); + write_option(f, "DNSDOMAIN", dev->dnsdomainname); + write_option(f, "NISDOMAIN", dev->nisdomainname); + write_option(f, "ROOTSERVER", + my_inet_ntoa(dev->ip_server)); + write_option(f, "ROOTPATH", dev->bootpath); + write_option(f, "filename", dev->filename); + sprintf(buf21, "%ld", (long)dev->uptime); + write_option(f, "UPTIME", buf21); + sprintf(buf21, "%u", (unsigned int)dev->dhcpleasetime); + write_option(f, "DHCPLEASETIME", buf21); + write_option(f, "DOMAINSEARCH", dev->domainsearch == NULL ? + "" : dev->domainsearch); + fclose(f); + } +} + +static uint32_t inet_class_netmask(uint32_t ip) +{ + ip = ntohl(ip); + if (IN_CLASSA(ip)) + return htonl(IN_CLASSA_NET); + if (IN_CLASSB(ip)) + return htonl(IN_CLASSB_NET); + if (IN_CLASSC(ip)) + return htonl(IN_CLASSC_NET); + return INADDR_ANY; +} + +static void postprocess_device(struct netdev *dev) +{ + if (dev->ip_netmask == INADDR_ANY) { + dev->ip_netmask = inet_class_netmask(dev->ip_addr); + printf("IP-Config: %s guessed netmask %s\n", + dev->name, my_inet_ntoa(dev->ip_netmask)); + } + if (dev->ip_broadcast == INADDR_ANY) { + dev->ip_broadcast = + (dev->ip_addr & dev->ip_netmask) | ~dev->ip_netmask; + printf("IP-Config: %s guessed broadcast address %s\n", + dev->name, my_inet_ntoa(dev->ip_broadcast)); + } +} + +static void complete_device(struct netdev *dev) +{ + struct sysinfo info; + + if (!sysinfo(&info)) + dev->uptime = info.uptime; + postprocess_device(dev); + configure_device(dev); + dump_device_config(dev); + print_device_config(dev); + packet_close(dev); + + ++configured; + + dev->next = ifaces; + ifaces = dev; +} + +/* + * Returns: + * 0 = Not handled, try again later + * 1 = Handled + */ +static int process_receive_event(struct state *s, time_t now) +{ + int handled = 1; + + switch (s->state) { + case DEVST_ERROR: + return 0; /* Not handled */ + case DEVST_COMPLETE: + return 0; /* Not handled as already configured */ + + case DEVST_BOOTP: + s->restart_state = DEVST_BOOTP; + switch (bootp_recv_reply(s->dev)) { + case -1: + s->state = DEVST_ERROR; + break; + case 0: + handled = 0; + break; + case 1: + s->state = DEVST_COMPLETE; + s->dev->proto = PROTO_BOOTP; + dprintf("\n bootp reply\n"); + break; + } + break; + + case DEVST_DHCPDISC: + s->restart_state = DEVST_DHCPDISC; + switch (dhcp_recv_offer(s->dev)) { + case -1: + s->state = DEVST_ERROR; + break; + case 0: + handled = 0; + break; + case DHCPOFFER: /* Offer received */ + s->state = DEVST_DHCPREQ; + dhcp_send_request(s->dev); + break; + } + break; + + case DEVST_DHCPREQ: + s->restart_state = DEVST_DHCPDISC; + switch (dhcp_recv_ack(s->dev)) { + case -1: /* error */ + s->state = DEVST_ERROR; + break; + case 0: + handled = 0; + break; + case DHCPACK: /* ACK received */ + s->state = DEVST_COMPLETE; + s->dev->proto = PROTO_DHCP; + break; + case DHCPNAK: /* NAK received */ + s->state = DEVST_DHCPDISC; + break; + } + break; + + default: + dprintf("\n"); + handled = 0; + break; + } + + switch (s->state) { + case DEVST_COMPLETE: + complete_device(s->dev); + break; + + case DEVST_ERROR: + /* error occurred, try again in 10 seconds */ + s->expire = now + 10; + break; + } + + return handled; +} + +static void process_timeout_event(struct state *s, time_t now) +{ + int ret = 0; + + /* + * If we had an error, restore a sane state to + * restart from. + */ + if (s->state == DEVST_ERROR) + s->state = s->restart_state; + + /* + * Now send a packet depending on our state. + */ + switch (s->state) { + case DEVST_BOOTP: + ret = bootp_send_request(s->dev); + s->restart_state = DEVST_BOOTP; + break; + + case DEVST_DHCPDISC: + ret = dhcp_send_discover(s->dev); + s->restart_state = DEVST_DHCPDISC; + break; + + case DEVST_DHCPREQ: + ret = dhcp_send_request(s->dev); + s->restart_state = DEVST_DHCPDISC; + break; + } + + if (ret == -1) { + s->state = DEVST_ERROR; + s->expire = now + 1; + } else { + s->expire = now + s->retry_period; + + s->retry_period *= 2; + if (s->retry_period > 60) + s->retry_period = 60; + } +} + +static void process_error_event(struct state *s, time_t now) +{ + s->state = DEVST_ERROR; + s->expire = now + 1; +} + +static struct state *slist; +struct netdev *ifaces; + +/* + * Returns: + * 0 = No dhcp/bootp packet was received + * 1 = A packet was received and handled + */ +static int do_pkt_recv(int nr, struct pollfd *fds, time_t now) +{ + int i, ret = 0; + struct state *s; + + for (i = 0, s = slist; s && nr; s = s->next) { + if (s->dev->pkt_fd != fds[i].fd) + continue; + if (fds[i].revents) { + if (fds[i].revents & POLLRDNORM) + ret |= process_receive_event(s, now); + else + process_error_event(s, now); + nr--; + } + i++; + } + return ret; +} + +static int loop(void) +{ + struct pollfd *fds; + struct state *s; + int i, nr = 0, rc = 0; + struct timeval now, prev; + time_t start; + + fds = malloc(sizeof(struct pollfd) * n_devices); + if (!fds) { + fprintf(stderr, "malloc failed\n"); + rc = -1; + goto bail; + } + + memset(fds, 0, sizeof(*fds)); + + gettimeofday(&now, NULL); + start = now.tv_sec; + while (1) { + int timeout = 60; + int pending = 0; + int done = 0; + int timeout_ms; + int x; + + for (i = 0, s = slist; s; s = s->next) { + dprintf("%s: state = %d\n", s->dev->name, s->state); + + if (s->state == DEVST_COMPLETE) { + done++; + continue; + } + + pending++; + + if (s->expire - now.tv_sec <= 0) { + dprintf("timeout\n"); + process_timeout_event(s, now.tv_sec); + } + + if (s->state != DEVST_ERROR) { + fds[i].fd = s->dev->pkt_fd; + fds[i].events = POLLRDNORM; + i++; + } + + if (timeout > s->expire - now.tv_sec) + timeout = s->expire - now.tv_sec; + } + + if (pending == 0 || (bringup_first && done)) + break; + + timeout_ms = timeout * 1000; + + for (x = 0; x < 2; x++) { + int delta_ms; + + if (timeout_ms <= 0) + timeout_ms = 100; + + nr = poll(fds, i, timeout_ms); + prev = now; + gettimeofday(&now, NULL); + + if ((nr > 0) && do_pkt_recv(nr, fds, now.tv_sec)) + break; + + if (loop_timeout >= 0 && + now.tv_sec - start >= loop_timeout) { + printf("IP-Config: no response after %d " + "secs - giving up\n", loop_timeout); + rc = -1; + goto bail; + } + + delta_ms = (now.tv_sec - prev.tv_sec) * 1000; + delta_ms += (now.tv_usec - prev.tv_usec) / 1000; + + dprintf("Delta: %d ms\n", delta_ms); + + timeout_ms -= delta_ms; + } + } +bail: + if (fds) + free(fds); + return rc; +} + +static int add_one_dev(struct netdev *dev) +{ + struct state *state; + + state = malloc(sizeof(struct state)); + if (!state) + return -1; + + state->dev = dev; + state->expire = time(NULL); + state->retry_period = 1; + + /* + * Select the state that we start from. + */ + if (dev->caps & CAP_DHCP && dev->ip_addr == INADDR_ANY) + state->restart_state = state->state = DEVST_DHCPDISC; + else if (dev->caps & CAP_DHCP) + state->restart_state = state->state = DEVST_DHCPREQ; + else if (dev->caps & CAP_BOOTP) + state->restart_state = state->state = DEVST_BOOTP; + + state->next = slist; + slist = state; + + n_devices++; + + return 0; +} + +static void parse_addr(uint32_t *addr, const char *ip) +{ + struct in_addr in; + if (inet_aton(ip, &in) == 0) { + fprintf(stderr, "%s: can't parse IP address '%s'\n", + progname, ip); + longjmp(abort_buf, 1); + } + *addr = in.s_addr; +} + +static unsigned int parse_proto(const char *ip) +{ + unsigned int caps = 0; + + if (*ip == '\0' || strcmp(ip, "on") == 0 || strcmp(ip, "any") == 0) + caps = CAP_BOOTP | CAP_DHCP | CAP_RARP; + else if (strcmp(ip, "both") == 0) + caps = CAP_BOOTP | CAP_RARP; + else if (strcmp(ip, "dhcp") == 0) + caps = CAP_BOOTP | CAP_DHCP; + else if (strcmp(ip, "bootp") == 0) + caps = CAP_BOOTP; + else if (strcmp(ip, "rarp") == 0) + caps = CAP_RARP; + else if (strcmp(ip, "none") == 0 || strcmp(ip, "static") == 0 + || strcmp(ip, "off") == 0) + goto bail; + else { + fprintf(stderr, "%s: invalid protocol '%s'\n", progname, ip); + longjmp(abort_buf, 1); + } +bail: + return caps; +} + +static int add_all_devices(struct netdev *template); + +static int parse_device(struct netdev *dev, char *ip) +{ + char *cp; + int opt; + int is_ip = 0; + + dprintf("IP-Config: parse_device: \"%s\"\n", ip); + + if (strncmp(ip, "ip=", 3) == 0) { + ip += 3; + is_ip = 1; + } else if (strncmp(ip, "nfsaddrs=", 9) == 0) { + ip += 9; + is_ip = 1; /* Not sure about this...? */ + } + + if (!strchr(ip, ':')) { + /* Only one option, e.g. "ip=dhcp", or an interface name */ + if (is_ip) { + dev->caps = parse_proto(ip); + bringup_first = 1; + } else { + dev->name = ip; + } + } else { + for (opt = 0; ip && *ip; ip = cp, opt++) { + if ((cp = strchr(ip, ':'))) { + *cp++ = '\0'; + } + if (*ip == '\0') + continue; + dprintf("IP-Config: opt #%d: '%s'\n", opt, ip); + switch (opt) { + case 0: + parse_addr(&dev->ip_addr, ip); + dev->caps = 0; + break; + case 1: + parse_addr(&dev->ip_server, ip); + break; + case 2: + parse_addr(&dev->ip_gateway, ip); + break; + case 3: + parse_addr(&dev->ip_netmask, ip); + break; + case 4: + strncpy(dev->hostname, ip, SYS_NMLN - 1); + dev->hostname[SYS_NMLN - 1] = '\0'; + memcpy(dev->reqhostname, dev->hostname, + SYS_NMLN); + break; + case 5: + dev->name = ip; + break; + case 6: + dev->caps = parse_proto(ip); + break; + case 7: + parse_addr(&dev->ip_nameserver[0], ip); + break; + case 8: + parse_addr(&dev->ip_nameserver[1], ip); + break; + case 9: + /* NTP server - ignore */ + break; + } + } + } + + if (dev->name == NULL || + dev->name[0] == '\0' || strcmp(dev->name, "all") == 0) { + add_all_devices(dev); + bringup_first = 1; + return 0; + } + return 1; +} + +static void bringup_device(struct netdev *dev) +{ + if (netdev_up(dev) == 0) { + if (dev->caps) + add_one_dev(dev); + else { + dev->proto = PROTO_NONE; + complete_device(dev); + } + } +} + +static void bringup_one_dev(struct netdev *template, struct netdev *dev) +{ + if (template->ip_addr != INADDR_NONE) + dev->ip_addr = template->ip_addr; + if (template->ip_server != INADDR_NONE) + dev->ip_server = template->ip_server; + if (template->ip_gateway != INADDR_NONE) + dev->ip_gateway = template->ip_gateway; + if (template->ip_netmask != INADDR_NONE) + dev->ip_netmask = template->ip_netmask; + if (template->ip_nameserver[0] != INADDR_NONE) + dev->ip_nameserver[0] = template->ip_nameserver[0]; + if (template->ip_nameserver[1] != INADDR_NONE) + dev->ip_nameserver[1] = template->ip_nameserver[1]; + if (template->hostname[0] != '\0') + strcpy(dev->hostname, template->hostname); + if (template->reqhostname[0] != '\0') + strcpy(dev->reqhostname, template->reqhostname); + dev->caps &= template->caps; + + bringup_device(dev); +} + +static struct netdev *add_device(char *info) +{ + struct netdev *dev; + int i; + + dev = malloc(sizeof(struct netdev)); + if (dev == NULL) { + fprintf(stderr, "%s: out of memory\n", progname); + longjmp(abort_buf, 1); + } + + memset(dev, 0, sizeof(struct netdev)); + dev->caps = default_caps; + + if (parse_device(dev, info) == 0) + goto bail; + + if (netdev_init_if(dev) == -1) + goto bail; + + if (bootp_init_if(dev) == -1) + goto bail; + + if (packet_open(dev) == -1) + goto bail; + + printf("IP-Config: %s hardware address", dev->name); + for (i = 0; i < dev->hwlen; i++) + printf("%c%02x", i == 0 ? ' ' : ':', dev->hwaddr[i]); + printf(" mtu %d%s%s\n", dev->mtu, + dev->caps & CAP_DHCP ? " DHCP" : + dev->caps & CAP_BOOTP ? " BOOTP" : "", + dev->caps & CAP_RARP ? " RARP" : ""); + return dev; +bail: + free(dev); + return NULL; +} + +static int add_all_devices(struct netdev *template) +{ + DIR *d; + struct dirent *de; + struct netdev *dev; + char t[PATH_MAX], p[255]; + int i, fd; + unsigned long flags; + + d = opendir(sysfs_class_net); + if (!d) + return 0; + + while ((de = readdir(d)) != NULL) { + /* This excludes devices beginning with dots or "dummy", + as well as . or .. */ + if (de->d_name[0] == '.' || !strcmp(de->d_name, "..")) + continue; + i = snprintf(t, PATH_MAX - 1, "%s/%s/flags", sysfs_class_net, + de->d_name); + if (i < 0 || i >= PATH_MAX - 1) + continue; + t[i] = '\0'; + fd = open(t, O_RDONLY); + if (fd < 0) { + perror(t); + continue; + } + i = read(fd, &p, sizeof(p) - 1); + close(fd); + if (i < 0) { + perror(t); + continue; + } + p[i] = '\0'; + flags = strtoul(p, NULL, 0); + /* Heuristic for if this is a reasonable boot interface. + This is the same + logic the in-kernel ipconfig uses... */ + if (!(flags & IFF_LOOPBACK) && + (flags & (IFF_BROADCAST | IFF_POINTOPOINT))) { + dprintf("Trying to bring up %s\n", de->d_name); + + dev = add_device(de->d_name); + if (!dev) + continue; + bringup_one_dev(template, dev); + } + } + closedir(d); + return 1; +} + +static int check_autoconfig(void) +{ + int ndev = 0, nauto = 0; + struct state *s; + + for (s = slist; s; s = s->next) { + ndev++; + if (s->dev->caps) + nauto++; + } + + if (ndev == 0) { + if (configured == 0) { + fprintf(stderr, "%s: no devices to configure\n", + progname); + longjmp(abort_buf, 1); + } + } + + return nauto; +} + +static void set_vendor_identifier(const char *id) +{ + int len = strlen(id); + if (len >= 255) { + fprintf(stderr, + "%s: invalid vendor class identifier: " + "%s\n", progname, id); + longjmp(abort_buf, 1); + } + memcpy(vendor_class_identifier+2, id, len); + vendor_class_identifier[0] = 60; + vendor_class_identifier[1] = len; + vendor_class_identifier_len = len+2; +} + +int main(int argc, char *argv[]) + __attribute__ ((weak, alias("ipconfig_main"))); + +int ipconfig_main(int argc, char *argv[]) +{ + struct netdev *dev; + int c, port; + int err = 0; + + /* If progname is set we're invoked from another program */ + if (!progname) { + struct timeval now; + progname = argv[0]; + gettimeofday(&now, NULL); + srand48(now.tv_usec ^ (now.tv_sec << 24)); + } + + if ((err = setjmp(abort_buf))) + return err; + + /* Default vendor identifier */ + set_vendor_identifier("Linux ipconfig"); + + do { + c = getopt(argc, argv, "c:d:i:onp:t:"); + if (c == EOF) + break; + + switch (c) { + case 'c': + default_caps = parse_proto(optarg); + break; + case 'p': + port = atoi(optarg); + if (port <= 0 || port > USHRT_MAX) { + fprintf(stderr, + "%s: invalid port number %d\n", + progname, port); + longjmp(abort_buf, 1); + } + cfg_local_port = port; + cfg_remote_port = cfg_local_port - 1; + break; + case 't': + loop_timeout = atoi(optarg); + if (loop_timeout < 0) { + fprintf(stderr, + "%s: invalid timeout %d\n", + progname, loop_timeout); + longjmp(abort_buf, 1); + } + break; + case 'i': + set_vendor_identifier(optarg); + break; + case 'o': + bringup_first = 1; + break; + case 'n': + do_not_config = 1; + break; + case 'd': + dev = add_device(optarg); + if (dev) + bringup_device(dev); + break; + case '?': + fprintf(stderr, "%s: invalid option -%c\n", + progname, optopt); + longjmp(abort_buf, 1); + } + } while (1); + + for (c = optind; c < argc; c++) { + dev = add_device(argv[c]); + if (dev) + bringup_device(dev); + } + + if (check_autoconfig()) { + if (cfg_local_port != LOCAL_PORT) { + printf("IP-Config: binding source port to %d, " + "dest to %d\n", + cfg_local_port, cfg_remote_port); + } + err = loop(); + } + + return err; +} diff --git a/usr/kinit/ipconfig/netdev.c b/usr/kinit/ipconfig/netdev.c new file mode 100644 index 0000000..de87f96 --- /dev/null +++ b/usr/kinit/ipconfig/netdev.c @@ -0,0 +1,279 @@ +/* + * ioctl-based device configuration + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <linux/route.h> +#include <linux/sockios.h> + +#include "netdev.h" + +static int cfd = -1; + +static void copy_name(struct netdev *dev, struct ifreq *ifr) +{ + strncpy(ifr->ifr_name, dev->name, sizeof(ifr->ifr_name)); + ifr->ifr_name[sizeof(ifr->ifr_name) - 1] = '\0'; +} + +int netdev_getflags(struct netdev *dev, short *flags) +{ + struct ifreq ifr; + + copy_name(dev, &ifr); + + if (ioctl(cfd, SIOCGIFFLAGS, &ifr) == -1) { + perror("SIOCGIFFLAGS"); + return -1; + } + + *flags = ifr.ifr_flags; + return 0; +} + +static int netdev_sif_addr(struct ifreq *ifr, int cmd, uint32_t addr) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = addr; + + memcpy(&ifr->ifr_addr, &sin, sizeof sin); + + return ioctl(cfd, cmd, ifr); +} + +int netdev_setaddress(struct netdev *dev) +{ + struct ifreq ifr; + + copy_name(dev, &ifr); + + if (dev->ip_addr != INADDR_ANY && + netdev_sif_addr(&ifr, SIOCSIFADDR, dev->ip_addr) == -1) { + perror("SIOCSIFADDR"); + return -1; + } + + if (dev->ip_broadcast != INADDR_ANY && + netdev_sif_addr(&ifr, SIOCSIFBRDADDR, dev->ip_broadcast) == -1) { + perror("SIOCSIFBRDADDR"); + return -1; + } + + if (dev->ip_netmask != INADDR_ANY && + netdev_sif_addr(&ifr, SIOCSIFNETMASK, dev->ip_netmask) == -1) { + perror("SIOCSIFNETMASK"); + return -1; + } + + return 0; +} + +static void set_s_addr(struct sockaddr *saddr, uint32_t ipaddr) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = ipaddr, + }; + memcpy(saddr, &sin, sizeof sin); +} + +int netdev_setroutes(struct netdev *dev) +{ + struct rtentry r; + + /* RFC3442 demands: + If the DHCP server returns both a Classless Static Routes option and + a Router option, the DHCP client MUST ignore the Router option. */ + if (dev->routes != NULL) { + struct route *cur; + for (cur = dev->routes; cur != NULL; cur = cur->next) { + memset(&r, 0, sizeof(r)); + + r.rt_dev = dev->name; + set_s_addr(&r.rt_dst, cur->subnet); + set_s_addr(&r.rt_gateway, cur->gateway); + set_s_addr(&r.rt_genmask, netdev_genmask(cur->netmask_width)); + r.rt_flags = RTF_UP; + if (cur->gateway != 0) { + r.rt_flags |= RTF_GATEWAY; + } + + if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) { + perror("SIOCADDRT"); + return -1; + } + } + } else if (dev->ip_gateway != INADDR_ANY) { + memset(&r, 0, sizeof(r)); + + set_s_addr(&r.rt_dst, INADDR_ANY); + set_s_addr(&r.rt_gateway, dev->ip_gateway); + set_s_addr(&r.rt_genmask, INADDR_ANY); + r.rt_flags = RTF_UP | RTF_GATEWAY; + + if (ioctl(cfd, SIOCADDRT, &r) == -1 && errno != EEXIST) { + perror("SIOCADDRT"); + return -1; + } + } + return 0; +} + +int netdev_setmtu(struct netdev *dev) +{ + struct ifreq ifr; + + copy_name(dev, &ifr); + ifr.ifr_mtu = dev->mtu; + + return ioctl(cfd, SIOCSIFMTU, &ifr); +} + +static int netdev_gif_addr(struct ifreq *ifr, int cmd, uint32_t * ptr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)&ifr->ifr_addr; + + if (ioctl(cfd, cmd, ifr) == -1) + return -1; + + *ptr = sin->sin_addr.s_addr; + + return 0; +} + +int netdev_up(struct netdev *dev) +{ + struct ifreq ifr; + + copy_name(dev, &ifr); + + if (ioctl(cfd, SIOCGIFFLAGS, &ifr) == -1) { + perror("SIOCGIFFLAGS"); + return -1; + } + + ifr.ifr_flags |= IFF_UP; + + if (ioctl(cfd, SIOCSIFFLAGS, &ifr) == -1) { + perror("SIOCSIFFLAGS"); + return -1; + } + return 0; +} + +int netdev_down(struct netdev *dev) +{ + struct ifreq ifr; + + copy_name(dev, &ifr); + + if (ioctl(cfd, SIOCGIFFLAGS, &ifr) == -1) { + perror("SIOCGIFFLAGS"); + return -1; + } + + ifr.ifr_flags &= ~IFF_UP; + + if (ioctl(cfd, SIOCSIFFLAGS, &ifr) == -1) { + perror("SIOCSIFFLAGS"); + return -1; + } + return 0; +} + +int netdev_init_if(struct netdev *dev) +{ + struct ifreq ifr; + + if (cfd == -1) + cfd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfd == -1) { + fprintf(stderr, "ipconfig: %s: socket(AF_INET): %s\n", + dev->name, strerror(errno)); + return -1; + } + + copy_name(dev, &ifr); + + if (ioctl(cfd, SIOCGIFINDEX, &ifr) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFINDEX: %s\n", + dev->name, strerror(errno)); + return -1; + } + + dev->ifindex = ifr.ifr_ifindex; + + if (ioctl(cfd, SIOCGIFMTU, &ifr) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFMTU: %s\n", + dev->name, strerror(errno)); + return -1; + } + + dev->mtu = ifr.ifr_mtu; + + if (ioctl(cfd, SIOCGIFHWADDR, &ifr) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFHWADDR: %s\n", + dev->name, strerror(errno)); + return -1; + } + + dev->hwtype = ifr.ifr_hwaddr.sa_family; + dev->hwlen = 0; + + switch (dev->hwtype) { + case ARPHRD_ETHER: + dev->hwlen = 6; + break; + case ARPHRD_EUI64: + dev->hwlen = 8; + break; + case ARPHRD_LOOPBACK: + dev->hwlen = 0; + break; + default: + return -1; + } + + memcpy(dev->hwaddr, ifr.ifr_hwaddr.sa_data, dev->hwlen); + memset(dev->hwbrd, 0xff, dev->hwlen); + + /* + * Try to get the current interface information. + */ + if (dev->ip_addr == INADDR_NONE && + netdev_gif_addr(&ifr, SIOCGIFADDR, &dev->ip_addr) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFADDR: %s\n", + dev->name, strerror(errno)); + dev->ip_addr = 0; + dev->ip_broadcast = 0; + dev->ip_netmask = 0; + return 0; + } + + if (dev->ip_broadcast == INADDR_NONE && + netdev_gif_addr(&ifr, SIOCGIFBRDADDR, &dev->ip_broadcast) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFBRDADDR: %s\n", + dev->name, strerror(errno)); + dev->ip_broadcast = 0; + } + + if (dev->ip_netmask == INADDR_NONE && + netdev_gif_addr(&ifr, SIOCGIFNETMASK, &dev->ip_netmask) == -1) { + fprintf(stderr, "ipconfig: %s: SIOCGIFNETMASK: %s\n", + dev->name, strerror(errno)); + dev->ip_netmask = 0; + } + + return 0; +} diff --git a/usr/kinit/ipconfig/netdev.h b/usr/kinit/ipconfig/netdev.h new file mode 100644 index 0000000..dbc80cd --- /dev/null +++ b/usr/kinit/ipconfig/netdev.h @@ -0,0 +1,107 @@ +#ifndef IPCONFIG_NETDEV_H +#define IPCONFIG_NETDEV_H + +#include <arpa/inet.h> +#include <sys/utsname.h> +#include <net/if.h> + +#define BPLEN 256 +#define FNLEN 128 /* from DHCP RFC 2131 */ + +struct route { + uint32_t subnet; /* subnet */ + uint32_t netmask_width; /* subnet mask width */ + uint32_t gateway; /* gateway */ + struct route *next; +}; + +struct netdev { + char *name; /* Device name */ + unsigned int ifindex; /* interface index */ + unsigned int hwtype; /* ARPHRD_xxx */ + unsigned int hwlen; /* HW address length */ + uint8_t hwaddr[16]; /* HW address */ + uint8_t hwbrd[16]; /* Broadcast HW address */ + unsigned int mtu; /* Device mtu */ + unsigned int caps; /* Capabilities */ + time_t open_time; + + struct { /* BOOTP/DHCP info */ + int fd; + uint32_t xid; + uint32_t gateway; /* BOOTP/DHCP gateway */ + } bootp; + + struct { /* RARP information */ + int fd; + } rarp; + + uint8_t proto; /* a protocol used (e.g. PROTO_DHCP) */ + uint32_t ip_addr; /* my address */ + uint32_t ip_broadcast; /* broadcast address */ + uint32_t ip_server; /* server address */ + uint32_t ip_netmask; /* my subnet mask */ + uint32_t ip_gateway; /* my gateway */ + uint32_t ip_nameserver[2]; /* two nameservers */ + uint32_t serverid; /* dhcp serverid */ + uint32_t dhcpleasetime; /* duration in seconds */ + char reqhostname[SYS_NMLN]; /* requested hostname */ + char hostname[SYS_NMLN]; /* hostname */ + char dnsdomainname[SYS_NMLN]; /* dns domain name */ + char nisdomainname[SYS_NMLN]; /* nis domain name */ + char bootpath[BPLEN]; /* boot path */ + char filename[FNLEN]; /* filename */ + char *domainsearch; /* decoded, NULL or malloc-ed */ + struct route *routes; /* decoded, NULL or malloc-ed list */ + long uptime; /* when complete configuration */ + int pkt_fd; /* packet socket for this interface */ + struct netdev *next; /* next configured i/f */ +}; + +extern struct netdev *ifaces; + +/* + * Device capabilities + */ +#define CAP_BOOTP (1<<0) +#define CAP_DHCP (1<<1) +#define CAP_RARP (1<<2) + +/* + * Device states + */ +#define DEVST_UP 0 +#define DEVST_BOOTP 1 +#define DEVST_DHCPDISC 2 +#define DEVST_DHCPREQ 3 +#define DEVST_COMPLETE 4 +#define DEVST_ERROR 5 + +int netdev_getflags(struct netdev *dev, short *flags); +int netdev_setaddress(struct netdev *dev); +int netdev_setroutes(struct netdev *dev); +int netdev_up(struct netdev *dev); +int netdev_down(struct netdev *dev); +int netdev_init_if(struct netdev *dev); +int netdev_setmtu(struct netdev *dev); + +static inline int netdev_running(struct netdev *dev) +{ + short flags; + int ret = netdev_getflags(dev, &flags); + + return ret ? 0 : !!(flags & IFF_RUNNING); +} + +static inline uint32_t netdev_genmask(uint32_t netmask_width) +{ + /* Map netmask width to network mask in network byte order. + Example: 24 -> "255.255.255.0" -> htonl(0xFFFFFF00) */ + if (netmask_width == 0) { + return 0; + } else { + return htonl(~((1u << (32 - netmask_width)) - 1)); + } +} + +#endif /* IPCONFIG_NETDEV_H */ diff --git a/usr/kinit/ipconfig/packet.c b/usr/kinit/ipconfig/packet.c new file mode 100644 index 0000000..2e1487d --- /dev/null +++ b/usr/kinit/ipconfig/packet.c @@ -0,0 +1,278 @@ +#include <errno.h>/*XXX*/ +/* + * Packet socket handling glue. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <net/if_packet.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <netpacket/packet.h> +#include <asm/byteorder.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/udp.h> + +#include "ipconfig.h" +#include "netdev.h" +#include "packet.h" + +uint16_t cfg_local_port = LOCAL_PORT; +uint16_t cfg_remote_port = REMOTE_PORT; + +int packet_open(struct netdev *dev) +{ + struct sockaddr_ll sll; + int fd, rv, one = 1; + + /* + * Get a PACKET socket for IP traffic. + */ + fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd == -1) { + perror("socket"); + return -1; + } + + /* + * We want to broadcast + */ + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, + sizeof(one)) == -1) { + perror("SO_BROADCAST"); + close(fd); + return -1; + } + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = dev->ifindex; + + rv = bind(fd, (struct sockaddr *)&sll, sizeof(sll)); + if (-1 == rv) { + perror("bind"); + close(fd); + return -1; + } + + dev->pkt_fd = fd; + return fd; +} + +void packet_close(struct netdev *dev) +{ + close(dev->pkt_fd); + dev->pkt_fd = -1; +} + +static unsigned int ip_checksum(uint16_t *hdr, int len) +{ + unsigned int chksum = 0; + + while (len) { + chksum += *hdr++; + chksum += *hdr++; + len--; + } + chksum = (chksum & 0xffff) + (chksum >> 16); + chksum = (chksum & 0xffff) + (chksum >> 16); + return (~chksum) & 0xffff; +} + +struct header { + struct iphdr ip; + struct udphdr udp; +} __attribute__ ((packed, aligned(4))); + +static struct header ipudp_hdrs = { + .ip = { + .ihl = 5, + .version = IPVERSION, + .frag_off = __constant_htons(IP_DF), + .ttl = 64, + .protocol = IPPROTO_UDP, + .saddr = INADDR_ANY, + .daddr = INADDR_BROADCAST, + }, + .udp = { + .source = __constant_htons(LOCAL_PORT), + .dest = __constant_htons(REMOTE_PORT), + .len = 0, + .check = 0, + }, +}; + +#ifdef DEBUG /* Only used with dprintf() */ +static char *ntoa(uint32_t addr) +{ + struct in_addr in = { addr }; + return inet_ntoa(in); +} +#endif /* DEBUG */ + +/* + * Send a packet. The options are listed in iov[1...iov_len-1]. + * iov[0] is reserved for the bootp packet header. + */ +int packet_send(struct netdev *dev, struct iovec *iov, int iov_len) +{ + struct sockaddr_ll sll; + struct msghdr msg; + int i, len = 0; + + memset(&sll, 0, sizeof(sll)); + msg.msg_name = &sll; + msg.msg_namelen = sizeof(sll); + msg.msg_iov = iov; + msg.msg_iovlen = iov_len; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if (cfg_local_port != LOCAL_PORT) { + ipudp_hdrs.udp.source = htons(cfg_local_port); + ipudp_hdrs.udp.dest = htons(cfg_remote_port); + } + + dprintf("\n udp src %d dst %d", ntohs(ipudp_hdrs.udp.source), + ntohs(ipudp_hdrs.udp.dest)); + + dprintf("\n ip src %s ", ntoa(ipudp_hdrs.ip.saddr)); + dprintf("dst %s ", ntoa(ipudp_hdrs.ip.daddr)); + + /* + * Glue in the ip+udp header iovec + */ + iov[0].iov_base = &ipudp_hdrs; + iov[0].iov_len = sizeof(struct header); + + for (i = 0; i < iov_len; i++) + len += iov[i].iov_len; + + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_IP); + sll.sll_ifindex = dev->ifindex; + sll.sll_hatype = dev->hwtype; + sll.sll_pkttype = PACKET_BROADCAST; + sll.sll_halen = dev->hwlen; + memcpy(sll.sll_addr, dev->hwbrd, dev->hwlen); + + ipudp_hdrs.ip.tot_len = htons(len); + ipudp_hdrs.ip.check = 0; + ipudp_hdrs.ip.check = ip_checksum((uint16_t *) &ipudp_hdrs.ip, + ipudp_hdrs.ip.ihl); + + ipudp_hdrs.udp.len = htons(len - sizeof(struct iphdr)); + + dprintf("\n bytes %d\n", len); + + return sendmsg(dev->pkt_fd, &msg, 0); +} + +void packet_discard(struct netdev *dev) +{ + struct iphdr iph; + struct sockaddr_ll sll; + socklen_t sllen = sizeof(sll); + + sll.sll_ifindex = dev->ifindex; + + recvfrom(dev->pkt_fd, &iph, sizeof(iph), 0, + (struct sockaddr *)&sll, &sllen); +} + +/* + * Receive a bootp packet. The options are listed in iov[1...iov_len]. + * iov[0] must point to the bootp packet header. + * Returns: + * -1 = Error, try again later +* 0 = Discarded packet (non-DHCP/BOOTP traffic) + * >0 = Size of packet + */ +int packet_recv(struct netdev *dev, struct iovec *iov, int iov_len) +{ + struct iphdr *ip, iph; + struct udphdr *udp; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = iov, + .msg_iovlen = iov_len, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0 + }; + int ret, iphl; + struct sockaddr_ll sll; + socklen_t sllen = sizeof(sll); + + sll.sll_ifindex = dev->ifindex; + msg.msg_name = &sll; + msg.msg_namelen = sllen; + + ret = recvfrom(dev->pkt_fd, &iph, sizeof(struct iphdr), + MSG_PEEK, (struct sockaddr *)&sll, &sllen); + if (ret == -1) + return -1; + + if (iph.ihl < 5 || iph.version != IPVERSION) + goto discard_pkt; + + iphl = iph.ihl * 4; + + ip = malloc(iphl + sizeof(struct udphdr)); + if (!ip) + goto discard_pkt; + + udp = (struct udphdr *)((char *)ip + iphl); + + iov[0].iov_base = ip; + iov[0].iov_len = iphl + sizeof(struct udphdr); + + ret = recvmsg(dev->pkt_fd, &msg, 0); + if (ret == -1) + goto free_pkt; + + dprintf("<- bytes %d ", ret); + + if (ip_checksum((uint16_t *) ip, ip->ihl) != 0) + goto free_pkt; + + dprintf("\n ip src %s ", ntoa(ip->saddr)); + dprintf("dst %s ", ntoa(ip->daddr)); + + if (ntohs(ip->tot_len) > ret || ip->protocol != IPPROTO_UDP) + goto free_pkt; + + ret -= 4 * ip->ihl; + + dprintf("\n udp src %d dst %d ", ntohs(udp->source), + ntohs(udp->dest)); + + if (udp->source != htons(cfg_remote_port) || + udp->dest != htons(cfg_local_port)) + goto free_pkt; + + if (ntohs(udp->len) > ret) + goto free_pkt; + + ret -= sizeof(struct udphdr); + + free(ip); + + return ret; + +free_pkt: + dprintf("freed\n"); + free(ip); + return 0; + +discard_pkt: + dprintf("discarded\n"); + packet_discard(dev); + return 0; +} diff --git a/usr/kinit/ipconfig/packet.h b/usr/kinit/ipconfig/packet.h new file mode 100644 index 0000000..4367efe --- /dev/null +++ b/usr/kinit/ipconfig/packet.h @@ -0,0 +1,12 @@ +#ifndef IPCONFIG_PACKET_H +#define IPCONFIG_PACKET_H + +struct iovec; + +int packet_open(struct netdev *dev); +void packet_close(struct netdev *dev); +int packet_send(struct netdev *dev, struct iovec *iov, int iov_len); +void packet_discard(struct netdev *dev); +int packet_recv(struct netdev *dev, struct iovec *iov, int iov_len); + +#endif /* IPCONFIG_PACKET_H */ |