// SPDX-License-Identifier: GPL-2.0-only // SPDX-FileCopyrightText: 1994, 1995, 2000 Neil Russell // SPDX-FileCopyrightText: 2000 Roland Borde // SPDX-FileCopyrightText: 2000 Paolo Scaffardi // SPDX-FileCopyrightText: 2000-2004 Wolfgang Denk, wd@denx.de /* * dhcp.c - barebox dhcp support * * based on U-Boot (LiMon) code */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define OPT_SIZE 312 /* Minimum DHCP Options size per RFC2131 - results in 576 byte pkt */ struct bootp { uint8_t bp_op; /* Operation */ #define OP_BOOTREQUEST 1 #define OP_BOOTREPLY 2 uint8_t bp_htype; /* Hardware type */ #define HWT_ETHER 1 uint8_t bp_hlen; /* Hardware address length */ #define HWL_ETHER 6 uint8_t bp_hops; /* Hop count (gateway thing) */ uint32_t bp_id; /* Transaction ID */ uint16_t bp_secs; /* Seconds since boot */ uint16_t bp_spare1; /* Alignment */ IPaddr_t bp_ciaddr; /* Client IP address */ IPaddr_t bp_yiaddr; /* Your (client) IP address */ IPaddr_t bp_siaddr; /* Server IP address */ IPaddr_t bp_giaddr; /* Gateway IP address */ uint8_t bp_chaddr[16]; /* Client hardware address */ char bp_sname[64]; /* Server host name */ char bp_file[128]; /* Boot file name */ char bp_vend[0]; /* Vendor information */ } __packed; /* DHCP States */ typedef enum { INIT, INIT_REBOOT, REBOOTING, SELECTING, REQUESTING, REBINDING, BOUND, RENEWING, } dhcp_state_t; #define DHCP_DISCOVER 1 #define DHCP_OFFER 2 #define DHCP_REQUEST 3 #define DHCP_DECLINE 4 #define DHCP_ACK 5 #define DHCP_NAK 6 #define DHCP_RELEASE 7 #define BOOTP_VENDOR_MAGIC 0x63825363 /* RFC1048 Magic Cookie */ #define PORT_BOOTPS 67 /* BOOTP server UDP port */ #define PORT_BOOTPC 68 /* BOOTP client UDP port */ #define DHCP_MIN_EXT_LEN 64 /* minimal length of extension list */ static uint32_t Bootp_id; static dhcp_state_t dhcp_state; static uint64_t dhcp_start; static struct eth_device *dhcp_edev; struct dhcp_req_param dhcp_param; struct dhcp_result *dhcp_result; struct dhcp_receivce_opts { IPaddr_t netmask; IPaddr_t gateway; IPaddr_t nameserver; IPaddr_t serverip; char *hostname; char *domainname; char *rootpath; char *devicetree; char *bootfile; }; #define DHCP_HOSTNAME 12 #define DHCP_VENDOR_ID 60 #define DHCP_CLIENT_ID 61 #define DHCP_USER_CLASS 77 #define DHCP_CLIENT_UUID 97 #define DHCP_OPTION224 224 static int dhcp_set_ip_options(int option, u8 *e, IPaddr_t ip) { int tmp; if (!ip) return 0; tmp = ntohl(ip); *e++ = option; *e++ = 4; *e++ = tmp >> 24; *e++ = tmp >> 16; *e++ = tmp >> 8; *e++ = tmp & 0xff; return 6; } static int bootp_check_packet(unsigned char *pkt, unsigned src, unsigned len) { struct bootp *bp = (struct bootp *) pkt; int retval = 0; if (src != PORT_BOOTPS) retval = -1; else if (len < sizeof(struct bootp)) retval = -2; else if (bp->bp_op != OP_BOOTREQUEST && bp->bp_op != OP_BOOTREPLY && bp->bp_op != DHCP_OFFER && bp->bp_op != DHCP_ACK && bp->bp_op != DHCP_NAK ) { retval = -3; } else if (bp->bp_htype != HWT_ETHER) retval = -4; else if (bp->bp_hlen != HWL_ETHER) retval = -5; else if (net_read_uint32(&bp->bp_id) != Bootp_id) { retval = -6; } debug("Filtering pkt = %d\n", retval); return retval; } /* * Copy parameters of interest from BOOTP_REPLY/DHCP_OFFER packet */ static void bootp_copy_net_params(struct bootp *bp) { dhcp_result->ip = net_read_ip(&bp->bp_yiaddr); dhcp_result->serverip = net_read_ip(&bp->bp_siaddr); if (strlen(bp->bp_file) > 0) dhcp_result->bootfile = xstrdup(bp->bp_file); } static int dhcp_set_string_options(int option, const char *str, u8 *e) { int str_len; if (!str) return 0; str_len = strlen(str); if (!str_len) return 0; *e++ = option; *e++ = str_len; memcpy(e, str, str_len); return str_len + 2; } /* * Initialize BOOTP extension fields in the request. */ static int dhcp_extended(u8 *e, int message_type, IPaddr_t ServerID, IPaddr_t RequestedIP) { int i; u8 *start = e; u8 *cnt; u8 dhcp_options[] = {1, 3, 6, DHCP_HOSTNAME, 15, 17, 51, DHCP_VENDOR_ID, 66, 67, DHCP_CLIENT_ID, DHCP_USER_CLASS, DHCP_CLIENT_UUID, 224}; *e++ = 99; /* RFC1048 Magic Cookie */ *e++ = 130; *e++ = 83; *e++ = 99; *e++ = 53; /* DHCP Message Type */ *e++ = 1; *e++ = message_type; *e++ = 57; /* Maximum DHCP Message Size */ *e++ = 2; *e++ = (576 - 312 + OPT_SIZE) >> 8; *e++ = (576 - 312 + OPT_SIZE) & 0xff; e += dhcp_set_ip_options(50, e, RequestedIP); e += dhcp_set_ip_options(54, e, ServerID); e += dhcp_set_string_options(DHCP_HOSTNAME, dhcp_param.hostname, e); e += dhcp_set_string_options(DHCP_VENDOR_ID, dhcp_param.vendor_id, e); e += dhcp_set_string_options(DHCP_CLIENT_ID, dhcp_param.client_id, e); e += dhcp_set_string_options(DHCP_USER_CLASS, dhcp_param.user_class, e); e += dhcp_set_string_options(DHCP_CLIENT_UUID, dhcp_param.client_uuid, e); e += dhcp_set_string_options(DHCP_OPTION224, dhcp_param.option224, e); *e++ = 55; /* Parameter Request List */ cnt = e++; /* Pointer to count of requested items */ for (i = 0; i < ARRAY_SIZE(dhcp_options); i++) *e++ = dhcp_options[i]; *cnt = ARRAY_SIZE(dhcp_options); *e++ = 255; /* End of the list */ /* Pad to minimal length */ while ((e - start) <= DHCP_MIN_EXT_LEN) *e++ = 0; return e - start; } static struct net_connection *dhcp_con; static int bootp_request(void) { struct bootp *bp; int ext_len; int ret; const char *bfile; dhcp_state = INIT; debug("BOOTP broadcast\n"); bp = net_udp_get_payload(dhcp_con);; bp->bp_op = OP_BOOTREQUEST; bp->bp_htype = HWT_ETHER; bp->bp_hlen = HWL_ETHER; bp->bp_hops = 0; bp->bp_secs = htons(get_time_ns() >> 30); net_write_ip(&bp->bp_ciaddr, 0); net_write_ip(&bp->bp_yiaddr, 0); net_write_ip(&bp->bp_siaddr, 0); net_write_ip(&bp->bp_giaddr, 0); memcpy(bp->bp_chaddr, dhcp_con->et->et_src, 6); bfile = getenv("bootfile"); if (bfile) safe_strncpy(bp->bp_file, bfile, sizeof(bp->bp_file)); /* Request additional information from the BOOTP/DHCP server */ ext_len = dhcp_extended(bp->bp_vend, DHCP_DISCOVER, 0, 0); Bootp_id = (uint32_t)get_time_ns(); net_copy_uint32(&bp->bp_id, &Bootp_id); dhcp_state = SELECTING; ret = net_udp_send(dhcp_con, sizeof(*bp) + ext_len); return ret; } static void dhcp_options_handle(unsigned char option, void *popt, int optlen, struct bootp *bp) { switch (option) { case 1: dhcp_result->netmask = net_read_ip(popt); break; case 3: dhcp_result->gateway = net_read_ip(popt); break; case 6: dhcp_result->nameserver = net_read_ip(popt); break; case DHCP_HOSTNAME: dhcp_result->hostname = xstrndup(popt, optlen); break; case 15: dhcp_result->domainname = xstrndup(popt, optlen); break; case 17: dhcp_result->rootpath = xstrndup(popt, optlen); break; case 51: net_copy_uint32(&dhcp_result->leasetime, popt); break; case 54: dhcp_result->dhcp_serverip = net_read_ip(popt); break; case 66: dhcp_result->tftp_server_name = xstrndup(popt, optlen); break; case 67: if (!dhcp_result->bootfile) dhcp_result->bootfile = xstrndup(popt, optlen); break; case 224: dhcp_result->devicetree = xstrndup(popt, optlen); break; default: debug("*** Unhandled DHCP Option in OFFER/ACK: %d\n", option); } } static void dhcp_options_process(unsigned char *popt, struct bootp *bp) { unsigned char *end = popt + sizeof(*bp) + OPT_SIZE; int oplen; unsigned char option; while (popt < end && *popt != 0xff) { oplen = *(popt + 1); option = *popt; dhcp_options_handle(option, popt + 2, oplen, bp); popt += oplen + 2; /* Process next option */ } } static int dhcp_message_type(unsigned char *popt) { if (net_read_uint32((uint32_t *)popt) != htonl(BOOTP_VENDOR_MAGIC)) return -1; popt += 4; while (*popt != 0xff) { if (*popt == 53) /* DHCP Message Type */ return *(popt + 2); popt += *(popt + 1) + 2; /* Scan through all options */ } return -1; } static void dhcp_send_request_packet(struct bootp *bp_offer) { struct bootp *bp; int extlen; debug("%s: Sending DHCPREQUEST\n", __func__); bp = net_udp_get_payload(dhcp_con); bp->bp_op = OP_BOOTREQUEST; bp->bp_htype = HWT_ETHER; bp->bp_hlen = HWL_ETHER; bp->bp_hops = 0; bp->bp_secs = htons(get_time_ns() >> 30); /* * RFC3046 requires Relay Agents to discard packets with * nonzero and offered giaddr */ net_write_ip(&bp->bp_giaddr, 0); memcpy(bp->bp_chaddr, dhcp_con->et->et_src, 6); /* * ID is the id of the OFFER packet */ net_copy_uint32(&bp->bp_id, &bp_offer->bp_id); /* * Copy options from OFFER packet if present */ extlen = dhcp_extended(bp->bp_vend, DHCP_REQUEST, dhcp_result->dhcp_serverip, dhcp_result->ip); debug("Transmitting DHCPREQUEST packet\n"); net_udp_send(dhcp_con, sizeof(*bp) + extlen); } /* * Handle DHCP received packets. */ static void dhcp_handler(void *ctx, char *packet, unsigned int len) { char *pkt = net_eth_to_udp_payload(packet); struct udphdr *udp = net_eth_to_udphdr(packet); struct bootp *bp = (struct bootp *)pkt; len = net_eth_to_udplen(packet); debug("DHCPHandler: got packet: (len=%d) state: %d\n", len, dhcp_state); if (bootp_check_packet(pkt, ntohs(udp->uh_sport), len)) /* Filter out pkts we don't want */ return; switch (dhcp_state) { case SELECTING: /* * Wait an appropriate time for any potential DHCPOFFER packets * to arrive. Then select one, and generate DHCPREQUEST response. * If filename is in format we recognize, assume it is a valid * OFFER from a server we want. */ debug("%s: state SELECTING, bp_file: \"%s\"\n", __func__, bp->bp_file); dhcp_state = REQUESTING; if (net_read_uint32(&bp->bp_vend[0]) == htonl(BOOTP_VENDOR_MAGIC)) dhcp_options_process((u8 *)&bp->bp_vend[4], bp); bootp_copy_net_params(bp); /* Store net params from reply */ dhcp_start = get_time_ns(); dhcp_send_request_packet(bp); break; case REQUESTING: debug("%s: State REQUESTING\n", __func__); if (dhcp_message_type((u8 *)bp->bp_vend) == DHCP_ACK ) { if (net_read_uint32(&bp->bp_vend[0]) == htonl(BOOTP_VENDOR_MAGIC)) dhcp_options_process(&bp->bp_vend[4], bp); bootp_copy_net_params(bp); /* Store net params from reply */ dhcp_state = BOUND; dev_info(&dhcp_edev->dev, "DHCP client bound to address %pI4\n", &dhcp_result->ip); return; } break; default: debug("%s: INVALID STATE\n", __func__); break; } } static char *global_dhcp_user_class; static char *global_dhcp_vendor_id; static char *global_dhcp_client_uuid; static char *global_dhcp_client_id; static char *global_dhcp_hostname; static char *global_dhcp_bootfile; static char *global_dhcp_oftree_file; static char *global_dhcp_rootpath; static char *global_dhcp_tftp_server_name; static int global_dhcp_retries = DHCP_DEFAULT_RETRY; static char *global_dhcp_option224; static void set_res(char **var, const char *res) { free(*var); if (res) *var = xstrdup(res); else *var = xstrdup(""); } int dhcp_request(struct eth_device *edev, const struct dhcp_req_param *param, struct dhcp_result **res) { int ret = 0; dhcp_edev = edev; if (param) dhcp_param = *param; else memset(&dhcp_param, 0, sizeof(dhcp_param)); dhcp_result = xzalloc(sizeof(*dhcp_result)); if (!dhcp_param.user_class) dhcp_param.user_class = global_dhcp_user_class; if (!dhcp_param.vendor_id) dhcp_param.vendor_id = global_dhcp_vendor_id; if (!dhcp_param.client_uuid) dhcp_param.client_uuid = global_dhcp_client_uuid; if (!dhcp_param.client_id) dhcp_param.client_id = global_dhcp_client_id; if (!dhcp_param.hostname) { if (global_dhcp_hostname && strlen(global_dhcp_hostname)) dhcp_param.hostname = global_dhcp_hostname; else dhcp_param.hostname = (char *)barebox_get_hostname(); } if (!dhcp_param.option224) dhcp_param.option224 = global_dhcp_option224; if (!dhcp_param.retries) dhcp_param.retries = global_dhcp_retries; dhcp_con = net_udp_eth_new(edev, IP_BROADCAST, PORT_BOOTPS, dhcp_handler, NULL); if (IS_ERR(dhcp_con)) { ret = PTR_ERR(dhcp_con); goto out; } ret = net_udp_bind(dhcp_con, PORT_BOOTPC); if (ret) goto out1; net_set_ip(edev, 0); dhcp_start = get_time_ns(); ret = bootp_request(); /* Basically same as BOOTP */ if (ret) goto out1; while (dhcp_state != BOUND) { if (ctrlc()) { ret = -EINTR; goto out1; } if (!dhcp_param.retries) { ret = -ETIMEDOUT; goto out1; } net_poll(); if (is_timeout(dhcp_start, 3 * SECOND)) { dhcp_start = get_time_ns(); printf("T "); ret = bootp_request(); /* no need to check if retries > 0 as we check if != 0 */ dhcp_param.retries--; if (ret) goto out1; } } pr_debug("DHCP result:\n" " ip: %pI4\n" " netmask: %pI4\n" " gateway: %pI4\n" " serverip: %pI4\n" " nameserver: %pI4\n" " hostname: %s\n" " domainname: %s\n" " rootpath: %s\n" " devicetree: %s\n" " tftp_server_name: %s\n", &dhcp_result->ip, &dhcp_result->netmask, &dhcp_result->gateway, &dhcp_result->serverip, &dhcp_result->nameserver, dhcp_result->hostname ? dhcp_result->hostname : "", dhcp_result->domainname ? dhcp_result->domainname : "", dhcp_result->rootpath ? dhcp_result->rootpath : "", dhcp_result->devicetree ? dhcp_result->devicetree : "", dhcp_result->tftp_server_name ? dhcp_result->tftp_server_name : ""); out1: net_unregister(dhcp_con); out: if (ret) { debug("dhcp failed: %s\n", strerror(-ret)); free(dhcp_result); } else { *res = dhcp_result; } return ret; } int dhcp_set_result(struct eth_device *edev, struct dhcp_result *res) { int ret; net_set_ip(edev, res->ip); net_set_netmask(edev, res->netmask); net_set_gateway(res->gateway); net_set_nameserver(res->nameserver); set_res(&global_dhcp_bootfile, res->bootfile); set_res(&global_dhcp_oftree_file, res->devicetree); set_res(&global_dhcp_rootpath, res->rootpath); set_res(&global_dhcp_tftp_server_name, res->tftp_server_name); if (res->hostname) barebox_set_hostname(res->hostname); if (res->domainname) net_set_domainname(res->domainname); if (res->tftp_server_name) { IPaddr_t ip; ret = resolv(res->tftp_server_name, &ip); if (!ret) net_set_serverip_empty(ip); } else if (res->serverip) { net_set_serverip_empty(res->serverip); } return 0; } void dhcp_result_free(struct dhcp_result *res) { free(res->hostname); free(res->domainname); free(res->rootpath); free(res->devicetree); free(res->bootfile); free(res->tftp_server_name); free(res); } int dhcp(struct eth_device *edev, const struct dhcp_req_param *param) { struct dhcp_result *res; int ret; ret = eth_open(edev); if (ret) return ret; ret = dhcp_request(edev, param, &res); if (ret) return ret; ret = dhcp_set_result(edev, res); dhcp_result_free(res); if (!ret) edev->ifup = true; return ret; } #ifdef CONFIG_GLOBALVAR static int dhcp_global_init(void) { globalvar_add_simple_string("dhcp.bootfile", &global_dhcp_bootfile); globalvar_add_simple_string("dhcp.rootpath", &global_dhcp_rootpath); global_dhcp_vendor_id = strdup("barebox"); globalvar_add_simple_string("dhcp.vendor_id", &global_dhcp_vendor_id); globalvar_add_simple_string("dhcp.client_uuid", &global_dhcp_client_uuid); globalvar_add_simple_string("dhcp.client_id", &global_dhcp_client_id); globalvar_add_simple_string("dhcp.hostname", &global_dhcp_hostname); globalvar_add_simple_string("dhcp.user_class", &global_dhcp_user_class); globalvar_add_simple_string("dhcp.oftree_file", &global_dhcp_oftree_file); globalvar_add_simple_string("dhcp.tftp_server_name", &global_dhcp_tftp_server_name); globalvar_add_simple_int("dhcp.retries", &global_dhcp_retries, "%u"); globalvar_add_simple_string("dhcp.option224", &global_dhcp_option224); return 0; } late_initcall(dhcp_global_init); #endif BAREBOX_MAGICVAR(global.dhcp.bootfile, "bootfile returned from DHCP request"); BAREBOX_MAGICVAR(global.dhcp.rootpath, "rootpath returned from DHCP request"); BAREBOX_MAGICVAR(global.dhcp.vendor_id, "vendor id to send to the DHCP server"); BAREBOX_MAGICVAR(global.dhcp.client_uuid, "client uuid to send to the DHCP server"); BAREBOX_MAGICVAR(global.dhcp.client_id, "client id to send to the DHCP server"); BAREBOX_MAGICVAR(global.dhcp.user_class, "user class to send to the DHCP server"); BAREBOX_MAGICVAR(global.dhcp.tftp_server_name, "TFTP server Name returned from DHCP request"); BAREBOX_MAGICVAR(global.dhcp.oftree_file, "OF tree returned from DHCP request (option 224)"); BAREBOX_MAGICVAR(global.dhcp.retries, "retry limit"); BAREBOX_MAGICVAR(global.dhcp.option224, "private data to send to the DHCP server (option 224)");