| /* |
| * Copyright (c) 2014 Brian Swetland |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #include "minip-internal.h" |
| |
| #include <err.h> |
| #include <platform.h> |
| #include <stdio.h> |
| #include <debug.h> |
| #include <malloc.h> |
| |
| #include <kernel/thread.h> |
| #include <sys/types.h> |
| #include <endian.h> |
| #include <string.h> |
| |
| #define TRACE_DHCP 0 |
| |
| typedef struct dhcp_msg { |
| u8 opcode; |
| u8 hwtype; // hw addr type |
| u8 hwalen; // hw addr length |
| u8 hops; |
| u32 xid; // txn id |
| u16 secs; // seconds since dhcp process start |
| u16 flags; |
| u32 ciaddr; // Client IP Address |
| u32 yiaddr; // Your IP Address |
| u32 siaddr; // Server IP Address |
| u32 giaddr; // Gateway IP Address |
| u8 chaddr[16]; // Client HW Address |
| u8 sname[64]; // Server Hostname, AsciiZ |
| u8 file[128]; // Boot File Name, AsciiZ |
| u32 cookie; |
| u8 options[0]; |
| } dhcp_msg_t; |
| |
| udp_socket_t *dhcp_udp_handle; |
| |
| #define DHCP_FLAG_BROADCAST 0x8000 |
| |
| #define DHCP_REQUEST 1 |
| #define DHCP_REPLY 2 |
| |
| #define OP_DHCPDISCOVER 1 // Client: Broadcast to find Server |
| #define OP_DHCPOFFER 2 // Server response to Discover |
| #define OP_DHCPREQUEST 3 // Client accepts offer |
| #define OP_DHCPDECLINE 4 // Client notifies address already in use |
| #define OP_DHCPACK 5 // Server confirms accept |
| #define OP_DHCPNAK 6 // Server disconfirms or lease expires |
| #define OP_DHCPRELEASE 7 // Client releases address |
| |
| #define OPT_NET_MASK 1 // len 4, mask |
| #define OPT_ROUTERS 3 // len 4n, gateway0, ... |
| #define OPT_DNS 6 // len 4n, nameserver0, ... |
| #define OPT_HOSTNAME 12 |
| #define OPT_REQUEST_IP 50 // len 4 |
| #define OPT_MSG_TYPE 53 // len 1, type same as op |
| #define OPT_SERVER_ID 54 // len 4, server ident ipaddr |
| #define OPT_DONE 255 |
| |
| #define DHCP_CLIENT_PORT 68 |
| #define DHCP_SERVER_PORT 67 |
| |
| #define HW_ETHERNET 1 |
| |
| static u8 mac[6]; |
| |
| static void printip(const char *name, u32 x) { |
| union { |
| u32 u; |
| u8 b[4]; |
| } ip; |
| ip.u = x; |
| printf("%s %d.%d.%d.%d\n", name, ip.b[0], ip.b[1], ip.b[2], ip.b[3]); |
| } |
| |
| static volatile int configured = 0; |
| static int cfgstate = 0; |
| |
| static void dhcp_discover(u32 xid) { |
| struct { |
| dhcp_msg_t msg; |
| u8 opt[128]; |
| } s; |
| u8 *opt = s.opt; |
| const char *hostname = minip_get_hostname(); |
| memset(&s, 0, sizeof(s)); |
| s.msg.opcode = DHCP_REQUEST; |
| s.msg.hwtype = HW_ETHERNET; |
| s.msg.hwalen = 6; |
| s.msg.xid = xid; |
| s.msg.cookie = 0x63538263; |
| minip_get_macaddr(s.msg.chaddr); |
| |
| *opt++ = OPT_MSG_TYPE; |
| *opt++ = 1; |
| *opt++ = OP_DHCPDISCOVER; |
| |
| if (hostname && hostname[0]) { |
| size_t len = strlen(hostname); |
| *opt++ = OPT_HOSTNAME; |
| *opt++ = len; |
| memcpy(opt, hostname, len); |
| opt += len; |
| } |
| |
| *opt++ = OPT_DONE; |
| |
| udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle); |
| status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle); |
| if (ret != NO_ERROR) { |
| printf("DHCP_DISCOVER failed: %d\n", ret); |
| } |
| } |
| |
| static void dhcp_request(u32 xid, u32 server, u32 reqip) { |
| struct { |
| dhcp_msg_t msg; |
| u8 opt[128]; |
| } s; |
| u8 *opt = s.opt; |
| const char *hostname = minip_get_hostname(); |
| memset(&s, 0, sizeof(s)); |
| s.msg.opcode = DHCP_REQUEST; |
| s.msg.hwtype = HW_ETHERNET; |
| s.msg.hwalen = 6; |
| s.msg.xid = xid; |
| s.msg.cookie = 0x63538263; |
| minip_get_macaddr(s.msg.chaddr); |
| |
| *opt++ = OPT_MSG_TYPE; |
| *opt++ = 1; |
| *opt++ = OP_DHCPREQUEST; |
| |
| *opt++ = OPT_SERVER_ID; |
| *opt++ = 4; |
| memcpy(opt, &server, 4); |
| opt += 4; |
| |
| *opt++ = OPT_REQUEST_IP; |
| *opt++ = 4; |
| memcpy(opt, &reqip, 4); |
| opt += 4; |
| |
| if (hostname && hostname[0]) { |
| size_t len = strlen(hostname); |
| *opt++ = OPT_HOSTNAME; |
| *opt++ = len; |
| memcpy(opt, hostname, len); |
| opt += len; |
| } |
| |
| *opt++ = OPT_DONE; |
| |
| status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle); |
| if (ret != NO_ERROR) { |
| printf("DHCP_REQUEST failed: %d\n", ret); |
| } |
| } |
| |
| static void dhcp_cb(void *data, size_t sz, uint32_t srcip, uint16_t srcport, void *arg) { |
| dhcp_msg_t *msg = data; |
| u8 *opt; |
| u32 netmask = 0; |
| u32 gateway = 0; |
| u32 dns = 0; |
| u32 server = 0; |
| int op = -1; |
| |
| if (sz < sizeof(dhcp_msg_t)) return; |
| |
| if (memcmp(msg->chaddr, mac, 6)) return; |
| |
| #if TRACE_DHCP |
| printf("dhcp op=%d len=%d from p=%d ip=", msg->opcode, sz, srcport); |
| printip("", srcip); |
| #endif |
| |
| if (configured) { |
| printf("already configured\n"); |
| return; |
| } |
| #if TRACE_DHCP |
| printip("ciaddr", msg->ciaddr); |
| printip("yiaddr", msg->yiaddr); |
| printip("siaddr", msg->siaddr); |
| printip("giaddr", msg->giaddr); |
| printf("chaddr %02x:%02x:%02x:%02x:%02x:%02x\n", |
| msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], |
| msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]); |
| #endif |
| sz -= sizeof(dhcp_msg_t); |
| opt = msg->options; |
| while (sz >= 2) { |
| sz -= 2; |
| if (opt[1] > sz) { |
| break; |
| } |
| #if TRACE_DHCP |
| printf("#%d (%d), ", opt[0], opt[1]); |
| #endif |
| switch (opt[0]) { |
| case OPT_MSG_TYPE: |
| if (opt[1] == 1) op = opt[2]; |
| break; |
| case OPT_NET_MASK: |
| if (opt[1] == 4) memcpy(&netmask, opt + 2, 4); |
| break; |
| case OPT_ROUTERS: |
| if (opt[1] >= 4) memcpy(&gateway, opt + 2, 4); |
| break; |
| case OPT_DNS: |
| if (opt[1] >= 4) memcpy(&dns, opt + 2, 4); |
| break; |
| case OPT_SERVER_ID: |
| if (opt[1] == 4) memcpy(&server, opt + 2, 4); |
| break; |
| case OPT_DONE: |
| goto done; |
| } |
| opt += opt[1] + 2; |
| sz -= opt[1]; |
| } |
| done: |
| #if TRACE_DHCP |
| printf("\n"); |
| if (server) printip("server", server); |
| if (netmask) printip("netmask", netmask); |
| if (gateway) printip("gateway", gateway); |
| if (dns) printip("dns", dns); |
| #endif |
| if (cfgstate == 0) { |
| if (op == OP_DHCPOFFER) { |
| printip("dhcp: offer:", msg->yiaddr); |
| if (server) { |
| dhcp_request(0xaabbccdd, server, msg->yiaddr); |
| cfgstate = 1; |
| } |
| } |
| } else if (cfgstate == 1) { |
| if (op == OP_DHCPACK) { |
| printip("dhcp: ack:", msg->yiaddr); |
| minip_set_ipaddr(msg->yiaddr); |
| configured = 1; |
| } |
| } |
| } |
| |
| static int dhcp_thread(void *arg) { |
| for (;;) { |
| if (configured) break; |
| thread_sleep(500); |
| if (configured) break; |
| dhcp_discover(0xaabbccdd); |
| } |
| return 0; |
| } |
| |
| static thread_t *dhcp_thr; |
| |
| void minip_init_dhcp(tx_func_t tx_func, void *tx_arg) { |
| minip_get_macaddr(mac); |
| |
| minip_init(tx_func, tx_arg, IPV4_NONE, IPV4_NONE, IPV4_NONE); |
| |
| int ret = udp_open(IPV4_BCAST, DHCP_CLIENT_PORT, DHCP_SERVER_PORT, &dhcp_udp_handle); |
| printf("dhcp opened udp: %d\n", ret); |
| |
| udp_listen(DHCP_CLIENT_PORT, dhcp_cb, NULL); |
| |
| dhcp_thr = thread_create("dhcp", dhcp_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE); |
| thread_detach_and_resume(dhcp_thr); |
| } |
| |
| // vim: set noexpandtab: |