diff --git a/Makefile b/Makefile index 441eb2eb..fb3960f6 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ libmount \ bench \ carray \ checksum \ +dhclient \ disked \ dnsconfig \ editor \ diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh index 24e5f447..b2ba49bc 100755 --- a/build-aux/iso-grub-cfg.sh +++ b/build-aux/iso-grub-cfg.sh @@ -118,6 +118,12 @@ system_initrd=$(maybe_compressed boot/system.initrd) ports=$(ls repository | sed 's/\.tix\.tar\.xz//') mkdir -p boot/grub +mkdir -p boot/grub/init + +echo "furthermore" > boot/grub/init/furthermore +# TODO: Possibly use a 'try' feature to not warn in case it was already unset. +echo "unset require dhclient" > boot/grub/init/network-no-dhclient + exec > boot/grub/grub.cfg for hook in \ @@ -184,6 +190,7 @@ else fi set enable_src=true set enable_network_drivers= +set enable_dhclient=true export version export machine @@ -194,6 +201,7 @@ export default export no_random_seed export enable_src export enable_network_drivers +export enable_dhclient EOF if [ -n "$ports" ]; then @@ -283,6 +291,12 @@ cat << EOF multiboot /$kernel \$no_random_seed \$enable_network_drivers "\$@" echo done hook_kernel_post + if ! \$enable_dhclient; then + echo -n "Disabling dhclient ... " + module /boot/grub/init/furthermore --create-to /etc/init/network + module /boot/grub/init/network-no-dhclient --append-to /etc/init/network + echo done + fi if [ \$no_random_seed != --no-random-seed ]; then echo -n "Loading /boot/random.seed (256) ... " module /boot/random.seed --random-seed @@ -430,6 +444,18 @@ else } fi +if \$enable_dhclient; then + menuentry "Disable DHCP client" { + enable_dhclient=false + configfile /boot/grub/advanced.cfg + } +else + menuentry "Enable DHCP client" { + enable_dhclient=true + configfile /boot/grub/advanced.cfg + } +fi + menuentry "Select binary packages..." { configfile /boot/grub/tix.cfg } diff --git a/dhclient/.gitignore b/dhclient/.gitignore new file mode 100644 index 00000000..5d468f9b --- /dev/null +++ b/dhclient/.gitignore @@ -0,0 +1 @@ +dhclient diff --git a/dhclient/Makefile b/dhclient/Makefile new file mode 100644 index 00000000..07599d82 --- /dev/null +++ b/dhclient/Makefile @@ -0,0 +1,29 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CFLAGS?=$(OPTLEVEL) + +CFLAGS += -Wall -Wextra + +BINARIES = dhclient +#MANPAGES8 = dhclient.8 + +all: $(BINARIES) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARIES) $(DESTDIR)$(SBINDIR) + #mkdir -p $(DESTDIR)$(MANDIR)/man8 + #install $(MANPAGES8) $(DESTDIR)$(MANDIR)/man8 + +%: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ + +clean: + rm -f $(BINARIES) diff --git a/dhclient/dhclient.c b/dhclient/dhclient.c new file mode 100644 index 00000000..fcd5976b --- /dev/null +++ b/dhclient/dhclient.c @@ -0,0 +1,798 @@ +/* + * Copyright (c) 2016, 2017 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * dhclient.c + * Dynamic Host Configuration Protocol client. + */ + +#if defined(__sortix__) +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT_DHCP_SERVER 67 +#define PORT_DHCP_CLIENT 68 + +struct dhcp +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint8_t ciaddr[4]; + uint8_t yiaddr[4]; + uint8_t siaddr[4]; + uint8_t giaddr[4]; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; +}; + +#define DHCP_OP_BOOTREQUEST 1 +#define DHCP_OP_BOOTREPLY 2 +#define DHCP_FLAGS_BROADCAST (1 << 15) +#define DHCP_HTYPE_ETHERNET 1 +#define DHCP_HLEN_ETHERNET 6 +#define DHCP_MAGIC_0 99 +#define DHCP_MAGIC_1 130 +#define DHCP_MAGIC_2 83 +#define DHCP_MAGIC_3 99 + +#define OPTION_PAD 0 +#define OPTION_SUBNET 1 +#define OPTION_TIME_OFFSET 2 +#define OPTION_ROUTERS 3 +#define OPTION_DNS 6 +#define OPTION_DOMAIN_NAME 12 +#define OPTION_INTERFACE_MTU 26 +#define OPTION_BROADCAST_ADDRESS 28 +#define OPTION_NTP 42 +#define OPTION_REQUESTED_IP 50 +#define OPTION_LEASE_TIME 51 +#define OPTION_OPTION_OVERLOAD 52 +#define OPTION_DHCP_MSGTYPE 53 +#define OPTION_SERVER_IDENTIFIER 54 +#define OPTION_PARAMETER_REQUEST 55 +#define OPTION_RENEWAL_TIME 58 +#define OPTION_REBINDING_TIME 59 +#define OPTION_END 255 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 9 + +struct dhcp_message +{ + struct dhcp hdr; + unsigned char magic[4]; + unsigned char options[65536 - (sizeof(struct dhcp) + 4)]; +}; + +enum option_state +{ + OPTION_STATE_OPTIONS, + OPTION_STATE_FILE, + OPTION_STATE_SNAME, + OPTION_STATE_DONE, +}; + +struct option_iterate +{ + struct dhcp* hdr; + unsigned char* options; + size_t offset; + size_t length; + enum option_state state; + bool has_sname_options; + bool has_file_options; +}; + +static void option_iterate_begin(struct option_iterate* iter, + struct dhcp* hdr, + unsigned char* options, + size_t length) +{ + memset(iter, 0, sizeof(*iter)); + iter->hdr = hdr; + iter->options = options; + iter->length = length; +} + +static void option_iterate_begin_msg(struct option_iterate* iter, + struct dhcp_message* msg, + size_t length) +{ + size_t offset = offsetof(struct dhcp_message, options); + assert(offset <= length); + option_iterate_begin(iter, &msg->hdr, msg->options, length - offset); +} + +static bool option_iterate_array(struct option_iterate* iter, + unsigned char* options, + size_t length, + unsigned char* out_option, + unsigned char* out_optlen, + unsigned char** out_data) +{ + while ( iter->offset < length ) + { + unsigned char option = options[iter->offset++]; + if ( option == OPTION_PAD ) + continue; + if ( option == OPTION_END ) + break; + if ( iter->offset == length ) + return false; + unsigned char optlen = options[iter->offset++]; + if ( length - iter->offset < optlen ) + return false; + unsigned char* data = iter->options + iter->offset; + *out_option = option; + *out_optlen = optlen; + *out_data = data; + iter->offset += optlen; + if ( option == OPTION_OPTION_OVERLOAD ) + { + if ( optlen != 1 ) + return false; + if ( iter->state == OPTION_STATE_OPTIONS ) + { + if ( data[0] & 1 << 0 ) + iter->has_sname_options = true; + if ( data[0] & 1 << 1 ) + iter->has_file_options = true; + } + continue; + } + return true; + } + return false; + +} + +static bool option_iterate(struct option_iterate* iter, + unsigned char* out_option, + unsigned char* out_optlen, + unsigned char** out_data) +{ + if ( iter->state == OPTION_STATE_OPTIONS ) + { + if ( option_iterate_array(iter, iter->options, iter->length, + out_option, out_optlen, out_data) ) + return true; + iter->state = OPTION_STATE_SNAME; + iter->offset = 0; + } + if ( iter->state == OPTION_STATE_SNAME ) + { + if ( iter->has_sname_options && + option_iterate_array(iter, iter->hdr->sname, + sizeof(iter->hdr->sname), out_option, + out_optlen, out_data) ) + return true; + iter->state = OPTION_STATE_FILE; + iter->offset = 0; + } + if ( iter->state == OPTION_STATE_FILE ) + { + if ( iter->has_file_options && + option_iterate_array(iter, iter->hdr->file, + sizeof(iter->hdr->file), out_option, + out_optlen, out_data) ) + return true; + iter->state = OPTION_STATE_DONE; + iter->offset = 0; + } + return false; +} + +static bool option_search(struct option_iterate* iter, + unsigned char search_option, + unsigned char* out_optlen, + unsigned char** out_data) +{ + enum option_state saved_state = iter->state; + size_t saved_offset = iter->offset; + iter->state = OPTION_STATE_OPTIONS; + iter->offset = 0; + bool result = false; + unsigned char option; + unsigned char optlen; + unsigned char* data; + while ( option_iterate(iter, &option, &optlen, &data) ) + { + if ( option == search_option ) + { + result = true; + *out_optlen = optlen; + *out_data = data; + break; + } + } + iter->state = saved_state; + iter->offset = saved_offset; + return result; +} + +static const unsigned char requests[] = +{ + OPTION_SUBNET, + OPTION_TIME_OFFSET, + OPTION_ROUTERS, + OPTION_DNS, + OPTION_DOMAIN_NAME, + OPTION_INTERFACE_MTU, + OPTION_NTP, +}; + +static size_t add_option_byte(unsigned char* options, + size_t optsmax, + size_t offset, + unsigned char byte) +{ + if ( optsmax <= offset ) + errx(1, "too many dhcp options"); + options[offset++] = byte; + return offset; +} + +static size_t add_option(unsigned char* options, + size_t optsmax, + size_t offset, + unsigned char option, + unsigned char optlen, + const unsigned char* data) +{ + offset = add_option_byte(options, optsmax, offset, option); + offset = add_option_byte(options, optsmax, offset, optlen); + for ( size_t i = 0; i < optlen; i++ ) + offset = add_option_byte(options, optsmax, offset, data[i]); + return offset; +} + +static bool check_dchp_message(struct dhcp_message* msg, + size_t amount, + unsigned char* chaddr, + uint32_t xid) +{ + if ( (size_t) amount < sizeof(msg->hdr) ) + return false; + if ( (size_t) amount < sizeof(msg->hdr) + sizeof(msg->magic) ) + return false; + if ( msg->hdr.op != DHCP_OP_BOOTREPLY ) + return false; + if ( msg->hdr.htype != DHCP_HTYPE_ETHERNET || + msg->hdr.hlen != DHCP_HLEN_ETHERNET ) + return false; + if ( memcmp(msg->hdr.chaddr, chaddr, sizeof(msg->hdr.chaddr)) != 0 ) + return false; + if ( msg->hdr.xid != htobe32(xid) ) + return false; + if ( msg->magic[0] != DHCP_MAGIC_0 || + msg->magic[1] != DHCP_MAGIC_1 || + msg->magic[2] != DHCP_MAGIC_2 || + msg->magic[3] != DHCP_MAGIC_3 ) + return false; + return true; +} + +static void ready(void) +{ + const char* readyfd_env = getenv("READYFD"); + if ( !readyfd_env ) + return; + int readyfd = atoi(readyfd_env); + char c = '\n'; + write(readyfd, &c, 1); + close(readyfd); + unsetenv("READYFD"); +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +int main(int argc, char* argv[]) +{ + setvbuf(stdout, NULL, _IOLBF, 0); + + const char* argv0 = argv[0]; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + char c; + while ( (c = *++arg) ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + exit(1); + } + } + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + exit(1); + } + } + + compact_arguments(&argc, &argv); + +#if defined(__sortix__) + if ( argc <= 1 ) + { + printf("Usage: %s \n", argv[0]); + return 0; + } + const char* if_name = argv[1]; + if ( 3 <= argc ) + errx(1, "unexpected extra operand `%s'", argv[2]); + + int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); + if ( dev_fd < 0 ) + err(1, "/dev"); + int if_fd = openat(dev_fd, if_name, O_RDWR); + if ( if_fd < 0 ) + err(1, "%s", if_name); + close(dev_fd); + + int type = ioctl(if_fd, IOCGETTYPE); + if ( type < 0 ) + err(1, "%s: ioctl: IOCGETTYPE", if_name); + if ( IOC_TYPE(type) != IOC_TYPE_NETWORK_INTERFACE ) + errx(1, "%s: Not a network interface", if_name); + + struct if_info info; + if ( ioctl(if_fd, NIOC_GETINFO, &info) < 0 ) + err(1, "%s: ioctl: NIOC_GETINFO", if_name); + if ( info.type == IF_TYPE_LOOPBACK ) + { + printf("%s: Loopback interface does not need to be configured\n", + if_name); + return 0; + } + if ( info.type != IF_TYPE_ETHERNET ) + errx(1, "%s: ioctl: NIOC_GETINFO: unknown device type", if_name); + if ( info.addrlen != 6 ) + errx(1, "%s: ioctl: NIOC_GETINFO: bogus address length", if_name); + // TODO: struct ether_addr + unsigned char ethaddr[6]; + memcpy(ethaddr, info.addr, 6); + +#else + unsigned char ethaddr[6] = { 00, 25, 22, 04, 95, 83 }; +#endif + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if ( fd < 0 ) + err(1, "socket"); + +#if defined(__sortix__) + if ( setsockopt(fd, SOL_SOCKET, SO_BINDTOINDEX, &info.linkid, + sizeof(info.linkid)) < 0 ) + err(1, "setsockopt: SO_BINDTOINDEX"); +#endif + + int enable = 1; + if ( setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable)) < 0 ) + err(1, "setsockopt: SO_BROADCAST"); + + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + local.sin_port = htobe16(PORT_DHCP_CLIENT); + local.sin_addr.s_addr = htobe32(INADDR_ANY); + + if ( bind(fd, (const struct sockaddr*) &local, sizeof(local)) < 0 ) + err(1, "bind"); + + struct sockaddr_in dest; + memset(&dest, 0, sizeof(dest)); + dest.sin_family = AF_INET; + dest.sin_port = htobe16(PORT_DHCP_SERVER); + dest.sin_addr.s_addr = htobe32(INADDR_BROADCAST); + + struct sockaddr_in remote; + socklen_t remote_len = sizeof(remote); + +#if defined(__sortix__) + printf("%s: Waiting for interface to come up\n", if_name); + // TODO: This can block indefinitely. Run ready() if this step times out. + if ( ioctl(if_fd, NIOC_WAITLINKSTATUS, 1) < 0 ) + err(1, "%s: ioctl: NIOC_WAITLINKSTATUS", if_name); + printf("%s: Interface is up\n", if_name); +#endif + + // TODO: "The client SHOULD wait a random time between one and ten seconds + // to desynchronize the use of DHCP at startup." + // Ten seconds seems excessive in today's world, but a small random + // delay seems reasonable. There are randomness in retransmissions so + // maybe this doesn't benefit so much. + +#if defined(__sortix__) + uint32_t xid = arc4random(); +#else + uint32_t xid = rand(); +#endif + + struct timespec start; + clock_gettime(CLOCK_MONOTONIC, &start); + unsigned int retransmissions = 0; + + unsigned char chaddr[16]; + memset(chaddr, 0, sizeof(chaddr)); + memcpy(chaddr, ethaddr, 6); + + static struct dhcp_message msg; + unsigned char* opts = msg.options; + size_t msgsize; + size_t optsoff; + size_t optsmax = sizeof(msg.options); + unsigned char option; + unsigned char optlen = 0; + unsigned char* optdata = NULL; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec begun = now; + struct timespec last_sent; + struct timespec timeout; + + last_sent = timespec_make(-1, 0); + timeout = timespec_make(0, 0); + + unsigned char yiaddr[4]; + unsigned char server_identifier[4]; + char remote_host_str[NI_MAXHOST]; + char remote_serv_str[NI_MAXSERV]; + char yiaddr_str[INET_ADDRSTRLEN]; + while ( true ) + { + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec since_sent = timespec_sub(now, last_sent); + if ( timespec_ge(since_sent, timeout) ) + { + struct timespec since_begun = timespec_sub(now, begun); + + memset(&msg, 0, sizeof(msg)); + msg.hdr.op = DHCP_OP_BOOTREQUEST; + msg.hdr.htype = DHCP_HTYPE_ETHERNET; + msg.hdr.hlen = DHCP_HLEN_ETHERNET; + msg.hdr.xid = htobe32(xid); + msg.hdr.secs = htobe16((uint16_t) since_begun.tv_sec); + msg.hdr.flags = htobe16(DHCP_FLAGS_BROADCAST); + memcpy(msg.hdr.chaddr, chaddr, sizeof(chaddr)); + msg.magic[0] = DHCP_MAGIC_0; + msg.magic[1] = DHCP_MAGIC_1; + msg.magic[2] = DHCP_MAGIC_2; + msg.magic[3] = DHCP_MAGIC_3; + + optsoff = 0; + option = DHCPDISCOVER; + optsoff = add_option(opts, optsmax, optsoff, OPTION_DHCP_MSGTYPE, + 1, &option); + optsoff = add_option(opts, optsmax, optsoff, + OPTION_PARAMETER_REQUEST, sizeof(requests), + requests); + // TODO: Maybe send hostname. + optsoff = add_option_byte(opts, optsmax, optsoff, OPTION_END); + + if ( retransmissions == 0 ) + printf("%s: Broadcasting DHCPDISCOVER\n", if_name); + else + printf("%s: Broadcasting DHCPDISCOVER (attempt %i)\n", if_name, + retransmissions + 1); + + msgsize = sizeof(msg.hdr) + sizeof(msg.magic) + optsoff; + // TODO: This can fail temporarily like with ENOBUFS. + if ( sendto(fd, &msg, msgsize, 0, (const struct sockaddr*) &dest, + sizeof(dest)) < 0 ) + err(1, "send"); + + last_sent = now; + timeout = timespec_make(1 << retransmissions, + arc4random_uniform(1000000000)); + if ( retransmissions < 6 ) + retransmissions++; + else + { + fprintf(stderr, "%s: error: DHCPDISCOVER timed out\n", if_name); + ready(); + // TODO: Technically restart the initialization. + retransmissions = 0; + } + } + struct timespec left = + timespec_sub(timespec_add(last_sent, timeout), now); + struct pollfd pfd = { 0 }; + pfd.fd = fd; + pfd.events = POLLIN; + int num_events = ppoll(&pfd, 1, &left, NULL); + if ( num_events < 0 ) + err(1, "ppoll"); + if ( num_events == 0 ) + continue; + ssize_t amount = recvfrom(fd, &msg, sizeof(msg), 0, + (struct sockaddr*) &remote, &remote_len); + if ( amount < 0 ) + err(1, "recv"); + // TODO: Check the remote port is correct? + if ( !check_dchp_message(&msg, amount, chaddr, xid) ) + continue; + struct option_iterate iter; + option_iterate_begin_msg(&iter, &msg, amount); + if ( !option_search(&iter, OPTION_DHCP_MSGTYPE, &optlen, &optdata) || + optlen != 1 || + optdata[0] != DHCPOFFER ) + continue; + if ( !option_search(&iter, OPTION_SERVER_IDENTIFIER, &optlen, + &optdata) || optlen != 4 ) + continue; + memcpy(server_identifier, optdata, 4); + memcpy(yiaddr, msg.hdr.yiaddr, 4); + getnameinfo((const struct sockaddr*) &remote, remote_len, + remote_host_str, sizeof(remote_host_str), + remote_serv_str, sizeof(remote_serv_str), + NI_NUMERICHOST | NI_NUMERICSERV); + inet_ntop(AF_INET, yiaddr, yiaddr_str, sizeof(yiaddr_str)); + printf("%s: DHCPOFFER of %s from %s:%s\n", + if_name, yiaddr_str, remote_host_str, remote_serv_str); + break; + } + + last_sent = timespec_make(-1, 0); + timeout = timespec_make(0, 0); + retransmissions = 0; + + unsigned char subnet[4]; + unsigned char router[4]; +#if defined(__sortix__) + size_t dns_count = 0; + unsigned char dns[DNSCONFIG_MAX_SERVERS][4]; +#endif + uint32_t lease_time = 0; + while ( true ) + { + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec since_sent = timespec_sub(now, last_sent); + if ( timespec_le(timeout, since_sent) ) + { + struct timespec since_begun = timespec_sub(now, begun); + + memset(&msg, 0, sizeof(msg)); + msg.hdr.op = DHCP_OP_BOOTREQUEST; + msg.hdr.htype = DHCP_HTYPE_ETHERNET; + msg.hdr.hlen = DHCP_HLEN_ETHERNET; + msg.hdr.xid = htobe32(xid); + msg.hdr.secs = htobe16((uint16_t) since_begun.tv_sec); + msg.hdr.flags = htobe16(DHCP_FLAGS_BROADCAST); + memcpy(msg.hdr.chaddr, chaddr, sizeof(chaddr)); + msg.magic[0] = DHCP_MAGIC_0; + msg.magic[1] = DHCP_MAGIC_1; + msg.magic[2] = DHCP_MAGIC_2; + msg.magic[3] = DHCP_MAGIC_3; + + optsoff = 0; + option = DHCPREQUEST; + optsoff = add_option(opts, optsmax, optsoff, OPTION_DHCP_MSGTYPE, + 1, &option); + optsoff = add_option(opts, optsmax, optsoff, + OPTION_PARAMETER_REQUEST, sizeof(requests), + requests); + optsoff = add_option(opts, optsmax, optsoff, + OPTION_SERVER_IDENTIFIER, + sizeof(server_identifier), server_identifier); + optsoff = add_option(opts, optsmax, optsoff, OPTION_REQUESTED_IP, + sizeof(yiaddr), yiaddr); + // TODO: Maybe send hostname. + optsoff = add_option_byte(opts, optsmax, optsoff, OPTION_END); + + if ( retransmissions == 0 ) + printf("%s: Broadcasting DHCPREQUEST\n", if_name); + else + printf("%s: Broadcasting DHCPREQUEST (attempt %i)\n", if_name, + retransmissions + 1); + + msgsize = sizeof(msg.hdr) + sizeof(msg.magic) + optsoff; + if ( sendto(fd, &msg, msgsize, 0, (const struct sockaddr*) &dest, + sizeof(dest)) < 0 ) + err(1, "send"); + + last_sent = now; + timeout = timespec_make(1 << retransmissions, + arc4random_uniform(1000000000)); + if ( retransmissions < 6 ) + retransmissions++; + else + { + fprintf(stderr, "%s: error: DHCPDISCOVER timed out\n", if_name); + ready(); + // TODO: Technically restart the initialization. + retransmissions = 0; + } + } + struct timespec left = + timespec_sub(timespec_add(last_sent, timeout), now); + struct pollfd pfd = { 0 }; + pfd.fd = fd; + pfd.events = POLLIN; + int num_events = ppoll(&pfd, 1, &left, NULL); + if ( num_events < 0 ) + err(1, "ppoll"); + if ( num_events == 0 ) + continue; + struct sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + ssize_t amount = recvfrom(fd, &msg, sizeof(msg), 0, + (struct sockaddr*) &peer, &peer_len); + if ( amount < 0 ) + err(1, "recv"); + if ( memcmp(&peer, &remote, remote_len) != 0 ) + continue; + if ( !check_dchp_message(&msg, amount, chaddr, xid) ) + continue; + struct option_iterate iter; + option_iterate_begin_msg(&iter, &msg, amount); + // TODO: Log proper errors for the below conditions. + if ( !option_search(&iter, OPTION_DHCP_MSGTYPE, &optlen, &optdata) || + optlen != 1 || + optdata[0] != DHCPACK ) + continue; + if ( !option_search(&iter, OPTION_SERVER_IDENTIFIER, &optlen, + &optdata) || optlen != 4 ) + continue; + memcpy(server_identifier, optdata, 4); + if ( !option_search(&iter, OPTION_SUBNET, &optlen, &optdata) || + optlen != 4 ) + continue; + memcpy(subnet, optdata, 4); + if ( !option_search(&iter, OPTION_ROUTERS, &optlen, &optdata) || + optlen < 4 ) + continue; + memcpy(router, optdata, 4); + if ( !option_search(&iter, OPTION_LEASE_TIME, &optlen, &optdata) || + optlen != 4 ) + continue; + lease_time = (uint32_t) optdata[0] << 24 | + (uint32_t) optdata[1] << 16 | + (uint32_t) optdata[2] << 8 | + (uint32_t) optdata[3] << 0; + // TODO: Verify yiaddr is what we requested (bait and switch). + memcpy(yiaddr, msg.hdr.yiaddr, 4); +#if defined(__sortix__) + if ( option_search(&iter, OPTION_DNS, &optlen, &optdata) ) + { + size_t offset = 0; + for ( ; dns_count < DNSCONFIG_MAX_SERVERS && 4 <= optlen - offset; + dns_count++ ) + { + dns[dns_count][0] = optdata[offset++]; + dns[dns_count][1] = optdata[offset++]; + dns[dns_count][2] = optdata[offset++]; + dns[dns_count][3] = optdata[offset++]; + } + } +#endif + printf("%s: DHCPACK of %s from %s:%s\n", + if_name, yiaddr_str, remote_host_str, remote_serv_str); + break; + } + + char router_str[INET_ADDRSTRLEN]; + char subnet_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, router, router_str, sizeof(router_str)); + inet_ntop(AF_INET, subnet, subnet_str, sizeof(subnet_str)); + printf("%s: Leased %s from %s:%s for %u seconds\n", + if_name, + yiaddr_str, + remote_host_str, + remote_serv_str, + lease_time); + printf("%s: Router is %s\n", if_name, router_str); + printf("%s: Subnet is %s\n", if_name, subnet_str); + if ( dns_count == 0 ) + printf("%s: No DNS servers were offered\n", if_name); + else for ( size_t i = 0; i < dns_count; i++ ) + { + char dns_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, dns[i], dns_str, sizeof(dns_str)); + printf("%s: DNS server %zu is %s\n", if_name, i + 1, dns_str); + } + +#if defined(__sortix__) + struct if_config_inet inet_cfg; + memcpy(&inet_cfg.address, yiaddr, sizeof(inet_cfg.address)); + memcpy(&inet_cfg.router, router, sizeof(inet_cfg.router)); + memcpy(&inet_cfg.subnet, subnet, sizeof(inet_cfg.subnet)); + if ( ioctl(if_fd, NIOC_SETCONFIG_INET, &inet_cfg) < 0 ) + err(1, "%s: ioctl: NIOC_SETCONFIG_INET", if_name); + // TODO: This configuration should set a bit that marks the interface as + // configured (and not in the configuration phase) and wake anything + // waiting for the interface to come up. + printf("%s: Configured network interface\n", if_name); + + struct dnsconfig dnsconfig; + memset(&dnsconfig, 0, sizeof(dnsconfig)); + dnsconfig.servers_count = dns_count; + for ( size_t i = 0; i < dns_count; i++ ) + { + dnsconfig.servers[i].family = AF_INET; + dnsconfig.servers[i].addrsize = 4; + memcpy(&dnsconfig.servers[i].addr, dns[i], 4); + } + if ( setdnsconfig(&dnsconfig) < 0 ) + err(1, "setdnsconfig"); + printf("%s: Configured DNS\n", if_name); +#endif + + ready(); + + while ( true ) + { + sleep(lease_time); + printf("%s: Lease for %s has expired after %u seconds\n", + if_name, yiaddr_str, lease_time); + // TODO: Attempt to renew lease and expire it and such. + fprintf(stderr, "error: Lease renewal is not implemented"); + } + + close(fd); +#if defined(__sortix__) + close(if_fd); +#endif +} diff --git a/dnsconfig/dnsconfig.8 b/dnsconfig/dnsconfig.8 index 935f6219..191a7ea5 100644 --- a/dnsconfig/dnsconfig.8 +++ b/dnsconfig/dnsconfig.8 @@ -76,7 +76,8 @@ Delete a resolver: .Sh SEE ALSO .Xr getdnsconfig 2 , .Xr setdnsconfig 2 , -.Xr inet 4 +.Xr inet 4 , +.Xr dhclient 8 .Sh HISTORY .Nm originally appeared in Sortix 1.1. @@ -85,3 +86,6 @@ The kernel DNS resolver list is global state. Changes made with .Nm may be overwritten by other programs. +In particular +.Xr dhclient 8 +sets the resolver list automatically unless otherwise configured. diff --git a/share/init/dhclient b/share/init/dhclient new file mode 100644 index 00000000..a8f34b33 --- /dev/null +++ b/share/init/dhclient @@ -0,0 +1,4 @@ +per if +#program=dhclient +#exec $program $if +exec dhclient diff --git a/share/init/network b/share/init/network index e69de29b..792ae583 100644 --- a/share/init/network +++ b/share/init/network @@ -0,0 +1 @@ +require dhclient diff --git a/share/man/man4/if.4 b/share/man/man4/if.4 index 72c30590..282e7f67 100644 --- a/share/man/man4/if.4 +++ b/share/man/man4/if.4 @@ -332,7 +332,8 @@ temporarily fail with .Xr inet 4 , .Xr ip 4 , .Xr lo 4 , -.Xr kernel 7 +.Xr kernel 7 , +.Xr dhclient 8 .Sh STANDARDS .St -p1003.1-2008 only specifies a minimal diff --git a/share/man/man5/init.5 b/share/man/man5/init.5 index 80f0da08..bc61c131 100644 --- a/share/man/man5/init.5 +++ b/share/man/man5/init.5 @@ -143,6 +143,13 @@ It depends on the and .Sy time daemons. +.It Sy dhclient +.\" TODO: The daemon becomes ready when an attempt has been made, rather than +.\" when it succeeded. +Daemon that starts +.Xr dhclient 8 +on each network interface and becomes ready when each network interface has +been configured. .It Sy local Virtual daemon that starts daemons pertinent to the local system. The system provides a default implementation that does nothing. @@ -154,6 +161,9 @@ system. .\" TODO: The daemon becomes ready when an attempt has been made, rather than .\" when it succeeded. Virtual daemon that becomes ready when the network has been configured. +It depends on the +.Sy dhclient +deamon. Daemons can depend on this daemon if they need the network to have been established before they start. .It Sy time @@ -482,6 +492,23 @@ The .Sy optional flag should only be omitted if a local daemon is critical and the boot should fail if the daemon fails. +.Ss Disable network auto-configuration (DHCP) +The +.Sy network +daemon depends by default on +.Sy dhclient , +which does DHCP configuration of the network. +This dependency can be removed by creating +.Pa /etc/init/network +with the following contents: +.Bd -literal +furthermore +unset require dhclient +.Ed +.Pp +This example extends the existing configuration in +.Pa /share/init/network +by removing a dependency. .Ss Creating a new virtual daemon The .Sy exampled diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7 index 5c54c29b..bf30b65c 100644 --- a/share/man/man7/installation.7 +++ b/share/man/man7/installation.7 @@ -82,7 +82,8 @@ per the instructions in The release modification procedure lets you customize aspects such as the default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to -the live environment, control which drivers are loaded by default, and so on. +the live environment, control which drivers are loaded by default, control which +live environment daemons are started by default, and so on. .Pp Warning: The live environment does not come with any random entropy and entropy gathering is not yet implemented. @@ -143,6 +144,29 @@ Only the selected ports are loaded into the live environment and installed onto the new installation. If upgrading an existing installation, then any ports not loaded will be removed from the installation being upgraded. +.Pp +Ports can additionally be loaded as binary packages in the +.Pa /repository +directory by navigating to the advanced menu and then the select binary packages +submenu and then selecting which ports. +.Pp +The network drivers can be disabled by navigating to the advanced menu and +selecting +.Sy Disable network drivers . +It can be useful to disable the network drivers if it's undesirable to put the +system on the network for security reasons. +You can disable network drivers by default by editing the bootloader +configuration as described below after completing the installation. +.Pp +By default +.Xr dhclient 8 +will automatically configure +.Xr ether 4 +network interfaces with DHCP and bring up network connectivity. +The DHCP client can be disabled by navigating to the advanced menu and selecting +.Sy Disable DHCP client , +which is useful if you want to manually configure the network or not expose the +system until you are ready. .Ss Installer This guide assumes you selected the operating system installation option in the bootloader. diff --git a/share/man/man7/release-iso-bootconfig.7 b/share/man/man7/release-iso-bootconfig.7 index d592d2ea..44363cf5 100644 --- a/share/man/man7/release-iso-bootconfig.7 +++ b/share/man/man7/release-iso-bootconfig.7 @@ -262,6 +262,8 @@ If the selected menu option itself is a submenu, it can be appended with a .Sy '>' and another selection to pick a default menu option in that submenu, and so on. (Default: 0) +.It Sy enable_dhclient +TODO: Document this. .It Sy enable_network_drivers An additional .Xr kernel 7 diff --git a/share/man/man7/release-iso-modification.7 b/share/man/man7/release-iso-modification.7 index c05b8973..3ed5a004 100644 --- a/share/man/man7/release-iso-modification.7 +++ b/share/man/man7/release-iso-modification.7 @@ -16,7 +16,8 @@ configuration as described in section 5 of the manual. The release modification procedure lets you customize aspects such as the default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to -the live environment, control which drivers are loaded by default, and so on. +the live environment, control which drivers are loaded by default, control which +live environment daemons are started by default, and so on. .Ss Prerequisites .Bl -bullet -compact .It @@ -399,6 +400,16 @@ security reasons or to work around driver issues: tix-iso-bootconfig --disable-network-drivers bootconfig tix-iso-add sortix.iso bootconfig .Ed +.Ss Disable DHCP Auto-Configuration By Default +To customize a release so +.Xr dhclient 8 +doesn't automatically configure network interfaces using DHCP, useful if one +wants to manually configure network interfaces with +.Xr ifconfig 8 . +.Bd -literal +tix-iso-bootconfig --disable-dhclient bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr development 7 , diff --git a/share/man/man7/upgrade.7 b/share/man/man7/upgrade.7 index ed28b54e..f9181efd 100644 --- a/share/man/man7/upgrade.7 +++ b/share/man/man7/upgrade.7 @@ -29,7 +29,8 @@ per the instructions in The release modification procedure lets you customize aspects such as the default bootloader menu option and timeout, the default hostname, the default keyboard layout, the default graphics resolution, adding files of your choice to -the live environment, control which drivers are loaded by default, and so on. +the live environment, control which drivers are loaded by default, control which +live environment daemons are started by default, and so on. .Pp Warning: The live environment does not come with any random entropy and entropy gathering is not yet implemented. diff --git a/tix/tix-iso-bootconfig b/tix/tix-iso-bootconfig index 857065b8..8976b92a 100755 --- a/tix/tix-iso-bootconfig +++ b/tix/tix-iso-bootconfig @@ -22,6 +22,7 @@ append_title="modified by $(id -un)@$(hostname)" default= directory= enable_append_title=true +enable_dhclient= enable_network_drivers= enable_src= liveconfig= @@ -51,9 +52,11 @@ for argument do --default=*) default=$parameter ;; --default) previous_option=default ;; --disable-append-title) enable_append_title=false ;; + --disable-dhclient) enable_dhclient=false ;; --disable-network-drivers) enable_network_drivers=false ;; --disable-src) enable_src=false ;; --enable-append-title) enable_append_title=true ;; + --enable-dhclient) enable_dhclient=true ;; --enable-network-drivers) enable_network_drivers=true ;; --enable-src) enable_src=true ;; --liveconfig=*) liveconfig=$parameter ;; @@ -135,6 +138,7 @@ mkdir -p -- "$directory/boot/grub" if [ -n "$timeout" ]; then printf 'timeout="%s"\n' "$timeout" fi + print_enable_default_bool "$enable_dhclient" dhclient dhclient print_enable_default "$enable_network_drivers" network_drivers network-drivers print_enable_default_bool "$enable_src" src src if $enable_append_title; then diff --git a/tix/tix-iso-bootconfig.8 b/tix/tix-iso-bootconfig.8 index 3b576e79..3158dd1a 100644 --- a/tix/tix-iso-bootconfig.8 +++ b/tix/tix-iso-bootconfig.8 @@ -9,9 +9,11 @@ .Op Fl \-append-title Ns = Ns Ar text .Op Fl \-default Ns = Ns Ar default-boot-menu-option .Op Fl \-disable-append-title +.Op Fl \-disable-dhclient .Op Fl \-disable-network-drivers .Op Fl \-disable-src .Op Fl \-enable-append-title +.Op Fl \-enable-dhclient .Op Fl \-enable-network-drivers .Op Fl \-enable-src .Op Fl \-liveconfig Ns = Ns Ar liveconfig-directory @@ -90,6 +92,14 @@ GRUB variable. Don't append anything to the bootloader menu title by appending to the .Sy base_menu_title GRUB variable. +.It Fl \-disable-dhclient +Disable automatic DHCP configuration by setting the +.Sy enable_dhclient +GRUB variable to +.Sy false , +causing the bootloader to load additional configuration that turns off the +.Xr dhclient 8 +daemon on boot. .It Fl \-disable-network-drivers Disable network drivers by setting the .Sy enable_network_drivers @@ -113,6 +123,14 @@ to the bootloader menu title by appending to the GRUB variable. This option is on by default and can be disabled with .Fl \-disable-append-title . +.It Fl \-enable-dhclient +Enable automatic DHCP configuration by setting the +.Sy enable_dhclient +GRUB variable to +.Sy true , +selecting the default behavior of starting the +.Xr dhclient 8 +daemon. .It Fl \-enable-network-drivers Enable network drivers by setting the .Sy enable_network_drivers @@ -254,10 +272,21 @@ security reasons or to work around driver issues: tix-iso-bootconfig --disable-network-drivers bootconfig tix-iso-add sortix.iso bootconfig .Ed +.Ss Disable DHCP Auto-Configuration By Default +To customize a release so +.Xr dhclient 8 +doesn't automatically configure network interfaces using DHCP, useful if one +wants to manually configure network interfaces with +.Xr ifconfig 8 . +.Bd -literal +tix-iso-bootconfig --disable-dhclient bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr kernel 7 , .Xr release-iso-bootconfig 7 , .Xr release-iso-modification 7 , +.Xr init 8 , .Xr tix-iso-add 8 , .Xr tix-iso-liveconfig 8