ambrop7 15 лет назад
Родитель
Сommit
0f4f47182e

+ 1 - 0
CMakeLists.txt

@@ -87,6 +87,7 @@ add_subdirectory(server_connection)
 add_subdirectory(security)
 add_subdirectory(socksclient)
 add_subdirectory(lwip)
+add_subdirectory(dhcpclient)
 if (NOT WIN32)
     add_subdirectory(ipc)
 endif ()

+ 298 - 0
dhcpclient/BDHCPClient.c

@@ -0,0 +1,298 @@
+/**
+ * @file BDHCPClient.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+
+#include <dhcpclient/BDHCPClient.h>
+
+#define COMPONENT_SOURCE 1
+#define COMPONENT_SINK 2
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define ETH_HEADER_LEN sizeof(struct ethernet_header)
+#define IPUDP_OVERHEAD (sizeof(struct ipv4_header) + sizeof(struct udp_header))
+
+static void error_handler (BDHCPClient *o, int component, const void *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (component) {
+        case COMPONENT_SOURCE: {
+            DEBUG("source error");
+        } break;
+        
+        case COMPONENT_SINK: {
+            DEBUG("sink error");
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+static int bind_to_device (int sock, const char *ifname)
+{
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+    if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int set_broadcast (int sock)
+{
+    int broadcast = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *)&broadcast, sizeof(broadcast)) < 0) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int get_iface_info (const char *ifname, uint8_t *out_mac, int *out_mtu, int *out_ifindex)
+{
+    struct ifreq ifr;
+    
+    int s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (!s) {
+        DEBUG("socket failed");
+        goto fail0;
+    }
+    
+    // get MAC
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+    if (ioctl(s, SIOCGIFHWADDR, &ifr)) {
+        DEBUG("ioctl(SIOCGIFHWADDR) failed");
+        goto fail1;
+    }
+    if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
+        DEBUG("hardware address not ethernet");
+        goto fail1;
+    }
+    memcpy(out_mac, ifr.ifr_hwaddr.sa_data, 6);
+    
+    // get MTU
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+    if (ioctl(s, SIOCGIFMTU, &ifr)) {
+        DEBUG("ioctl(SIOCGIFMTU) failed");
+        goto fail1;
+    }
+    *out_mtu = ifr.ifr_mtu;
+    
+    // get interface index
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+    if (ioctl(s, SIOCGIFINDEX, &ifr)) {
+        DEBUG("ioctl(SIOCGIFINDEX) failed");
+        goto fail1;
+    }
+    *out_ifindex = ifr.ifr_ifindex;
+    
+    close(s);
+    
+    return 1;
+    
+fail1:
+    close(s);
+fail0:
+    return 0;
+}
+
+int BDHCPClient_Init (BDHCPClient *o, const char *ifname, BReactor *reactor, BDHCPClientCore_handler handler, void *user)
+{
+    // init arguments
+    o->reactor = reactor;
+    
+    // get interface information
+    uint8_t if_mac[6];
+    int if_mtu;
+    int if_index;
+    if (!get_iface_info(ifname, if_mac, &if_mtu, &if_index)) {
+        DEBUG("failed to get interface information");
+        goto fail0;
+    }
+    
+    DEBUG("if_mac=%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8" if_mtu=%d if_index=%d",
+          if_mac[0], if_mac[1], if_mac[2], if_mac[3], if_mac[4], if_mac[5], if_mtu, if_index);
+    
+    if (if_mtu < IPUDP_OVERHEAD) {
+        DEBUG("MTU is too small for UDP/IP !?!");
+        goto fail0;
+    }
+    
+    int dhcp_mtu = if_mtu - IPUDP_OVERHEAD;
+    
+    // init socket
+    if (BSocket_Init(&o->sock, o->reactor, BADDR_TYPE_PACKET, BSOCKET_TYPE_DGRAM) < 0) {
+        DEBUG("BSocket_Init failed");
+        goto fail0;
+    }
+    
+    // set socket broadcast
+    if (!set_broadcast(o->sock.socket)) {
+        DEBUG("set_broadcast failed");
+        goto fail1;
+    }
+    
+    // bind socket to device
+    if (!bind_to_device(o->sock.socket, ifname)) {
+        DEBUG("bind_to_device failed");
+        goto fail1;
+    }
+    
+    // bind socket
+    BAddr bind_addr;
+    BAddr_InitPacket(&bind_addr, hton16(ETHERTYPE_IPV4), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_HOST, if_mac);
+    if (BSocket_Bind(&o->sock, &bind_addr) < 0) {
+        DEBUG("BSocket_Bind failed");
+        goto fail1;
+    }
+    
+    // init error handler
+    FlowErrorDomain_Init(&o->domain, (FlowErrorDomain_handler)error_handler, o);
+    
+    // init sending
+    
+    // init sink
+    BAddr dest_addr;
+    uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    BAddr_InitPacket(&dest_addr, hton16(ETHERTYPE_IPV4), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_BROADCAST, broadcast_mac);
+    BIPAddr local_addr;
+    BIPAddr_InitInvalid(&local_addr);
+    DatagramSocketSink_Init(&o->send_sink, FlowErrorReporter_Create(&o->domain, COMPONENT_SINK), &o->sock, if_mtu, dest_addr, local_addr, BReactor_PendingGroup(o->reactor));
+    
+    // init copier
+    PacketCopier_Init(&o->send_copier, dhcp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init encoder
+    DHCPIpUdpEncoder_Init(&o->send_encoder, PacketCopier_GetOutput(&o->send_copier), BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->send_buffer, DHCPIpUdpEncoder_GetOutput(&o->send_encoder), DatagramSocketSink_GetInput(&o->send_sink), BReactor_PendingGroup(o->reactor))) {
+        DEBUG("SinglePacketBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // init receiving
+    
+    // init source
+    DatagramSocketSource_Init(&o->recv_source, FlowErrorReporter_Create(&o->domain, COMPONENT_SOURCE), &o->sock, if_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init copier
+    PacketCopier_Init(&o->recv_copier, dhcp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init decoder
+    DHCPIpUdpDecoder_Init(&o->recv_decoder, PacketCopier_GetInput(&o->recv_copier), BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->recv_buffer, DatagramSocketSource_GetOutput(&o->recv_source), DHCPIpUdpDecoder_GetInput(&o->recv_decoder), BReactor_PendingGroup(o->reactor))) {
+        DEBUG("SinglePacketBuffer_Init failed");
+        goto fail3;
+    }
+    
+    // init dhcp
+    if (!BDHCPClientCore_Init(&o->dhcp, PacketCopier_GetInput(&o->send_copier), PacketCopier_GetOutput(&o->recv_copier), if_mac, o->reactor, handler, user)) {
+        DEBUG("BDHCPClientCore_Init failed");
+        goto fail4;
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail4:
+    SinglePacketBuffer_Free(&o->recv_buffer);
+fail3:
+    DHCPIpUdpDecoder_Free(&o->recv_decoder);
+    PacketCopier_Free(&o->recv_copier);
+    DatagramSocketSource_Free(&o->recv_source);
+    SinglePacketBuffer_Free(&o->send_buffer);
+fail2:
+    DHCPIpUdpEncoder_Free(&o->send_encoder);
+    PacketCopier_Free(&o->send_copier);
+    DatagramSocketSink_Free(&o->send_sink);
+fail1:
+    BSocket_Free(&o->sock);
+fail0:
+    return 0;
+}
+
+void BDHCPClient_Free (BDHCPClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free dhcp
+    BDHCPClientCore_Free(&o->dhcp);
+    
+    // free receiving
+    SinglePacketBuffer_Free(&o->recv_buffer);
+    DHCPIpUdpDecoder_Free(&o->recv_decoder);
+    PacketCopier_Free(&o->recv_copier);
+    DatagramSocketSource_Free(&o->recv_source);
+    
+    // free sending
+    SinglePacketBuffer_Free(&o->send_buffer);
+    DHCPIpUdpEncoder_Free(&o->send_encoder);
+    PacketCopier_Free(&o->send_copier);
+    DatagramSocketSink_Free(&o->send_sink);
+    
+    // free socket
+    BSocket_Free(&o->sock);
+}
+
+void BDHCPClient_GetClientIP (BDHCPClient *o, uint32_t *out_ip)
+{
+    BDHCPClientCore_GetClientIP(&o->dhcp, out_ip);
+}
+
+void BDHCPClient_GetClientMask (BDHCPClient *o, uint32_t *out_mask)
+{
+    BDHCPClientCore_GetClientMask(&o->dhcp, out_mask);
+}
+
+int BDHCPClient_GetRouter (BDHCPClient *o, uint32_t *out_router)
+{
+    return BDHCPClientCore_GetRouter(&o->dhcp, out_router);
+}
+
+int BDHCPClient_GetDNS (BDHCPClient *o, uint32_t *out_dns_servers, size_t max_dns_servers)
+{
+    return BDHCPClientCore_GetDNS(&o->dhcp, out_dns_servers, max_dns_servers);
+}

+ 66 - 0
dhcpclient/BDHCPClient.h

@@ -0,0 +1,66 @@
+/**
+ * @file BDHCPClient.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * DHCP client.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_BDHCPCLIENT_H
+#define BADVPN_DHCPCLIENT_BDHCPCLIENT_H
+
+#include <system/DebugObject.h>
+#include <system/BSocket.h>
+#include <flow/PacketCopier.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/DatagramSocketSink.h>
+#include <flow/DatagramSocketSource.h>
+#include <dhcpclient/BDHCPClientCore.h>
+#include <dhcpclient/DHCPIpUdpDecoder.h>
+#include <dhcpclient/DHCPIpUdpEncoder.h>
+
+typedef struct {
+    BReactor *reactor;
+    BSocket sock;
+    FlowErrorDomain domain;
+    
+    PacketCopier send_copier;
+    DHCPIpUdpEncoder send_encoder;
+    SinglePacketBuffer send_buffer;
+    DatagramSocketSink send_sink;
+    
+    DatagramSocketSource recv_source;
+    SinglePacketBuffer recv_buffer;
+    DHCPIpUdpDecoder recv_decoder;
+    PacketCopier recv_copier;
+    
+    BDHCPClientCore dhcp;
+    DebugObject d_obj;
+} BDHCPClient;
+
+int BDHCPClient_Init (BDHCPClient *o, const char *ifname, BReactor *reactor, BDHCPClientCore_handler handler, void *user);
+void BDHCPClient_Free (BDHCPClient *o);
+void BDHCPClient_GetClientIP (BDHCPClient *o, uint32_t *out_ip);
+void BDHCPClient_GetClientMask (BDHCPClient *o, uint32_t *out_mask);
+int BDHCPClient_GetRouter (BDHCPClient *o, uint32_t *out_router);
+int BDHCPClient_GetDNS (BDHCPClient *o, uint32_t *out_dns_servers, size_t max_dns_servers);
+
+#endif

+ 673 - 0
dhcpclient/BDHCPClientCore.c

@@ -0,0 +1,673 @@
+/**
+ * @file BDHCPClientCore.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/byteorder.h>
+#include <misc/minmax.h>
+#include <security/BRandom.h>
+
+#include <dhcpclient/BDHCPClientCore.h>
+
+#define RESET_TIMEOUT 10000
+#define REQUEST_TIMEOUT 3000
+#define RENEW_REQUEST_TIMEOUT 20000
+
+#define MAX_REQUESTS 4
+
+#define STATE_RESETTING 1
+#define STATE_SENT_DISCOVER 2
+#define STATE_SENT_REQUEST 3
+#define STATE_FINISHED 4
+#define STATE_RENEWING 5
+
+#define IP_UDP_HEADERS_SIZE 28
+
+static void report_up (BDHCPClientCore *o)
+{
+    o->handler(o->user, BDHCPCLIENTCORE_EVENT_UP);
+    return;
+}
+
+static void report_down (BDHCPClientCore *o)
+{
+    o->handler(o->user, BDHCPCLIENTCORE_EVENT_DOWN);
+    return;
+}
+
+static void send_message (
+    BDHCPClientCore *o,
+    int type,
+    uint32_t xid,
+    int have_requested_ip_address, uint32_t requested_ip_address,
+    int have_dhcp_server_identifier, uint32_t dhcp_server_identifier
+)
+{
+    ASSERT(type == DHCP_MESSAGE_TYPE_DISCOVER || type == DHCP_MESSAGE_TYPE_REQUEST)
+    
+    if (o->sending) {
+        DEBUG("already sending!");
+        return;
+    }
+    
+    // write header
+    memset(o->send_buf, 0, sizeof(*o->send_buf));
+    o->send_buf->op = hton8(DHCP_OP_BOOTREQUEST);
+    o->send_buf->htype = hton8(DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET);
+    o->send_buf->hlen = hton8(6);
+    o->send_buf->xid = xid;
+    o->send_buf->secs = hton16(0);
+    memcpy(o->send_buf->chaddr, o->client_mac_addr, sizeof(o->client_mac_addr));
+    o->send_buf->magic = hton32(DHCP_MAGIC);
+    
+    // write options
+    
+    struct dhcp_option_header *out = (void *)(o->send_buf + 1);
+    
+    // DHCP message type
+    out->type = hton8(DHCP_OPTION_DHCP_MESSAGE_TYPE);
+    out->len = hton8(sizeof(struct dhcp_option_dhcp_message_type));
+    ((struct dhcp_option_dhcp_message_type *)(out + 1))->type = hton8(type);
+    out = (void *)((uint8_t *)(out + 1) + ntoh8(out->len));
+    
+    if (have_requested_ip_address) {
+        // requested IP address
+        out->type = hton8(DHCP_OPTION_REQUESTED_IP_ADDRESS);
+        out->len = hton8(sizeof(struct dhcp_option_addr));
+        ((struct dhcp_option_addr *)(out + 1))->addr = requested_ip_address;
+        out = (void *)((uint8_t *)(out + 1) + ntoh8(out->len));
+    }
+    
+    if (have_dhcp_server_identifier) {
+        // DHCP server identifier
+        out->type = hton8(DHCP_OPTION_DHCP_SERVER_IDENTIFIER);
+        out->len = hton8(sizeof(struct dhcp_option_dhcp_server_identifier));
+        ((struct dhcp_option_dhcp_server_identifier *)(out + 1))->id = dhcp_server_identifier;
+        out = (void *)((uint8_t *)(out + 1) + ntoh8(out->len));
+    }
+    
+    // maximum message size
+    out->type = hton8(DHCP_OPTION_MAXIMUM_MESSAGE_SIZE);
+    out->len = hton8(sizeof(struct dhcp_option_maximum_message_size));
+    ((struct dhcp_option_maximum_message_size *)(out + 1))->size = hton16(IP_UDP_HEADERS_SIZE + PacketRecvInterface_GetMTU(o->recv_if));
+    out = (void *)((uint8_t *)(out + 1) + ntoh8(out->len));
+    
+    // parameter request list
+    out->type = hton8(DHCP_OPTION_PARAMETER_REQUEST_LIST);
+    out->len = hton8(4);
+    ((uint8_t *)(out + 1))[0] = DHCP_OPTION_SUBNET_MASK;
+    ((uint8_t *)(out + 1))[1] = DHCP_OPTION_ROUTER;
+    ((uint8_t *)(out + 1))[2] = DHCP_OPTION_DOMAIN_NAME_SERVER;
+    ((uint8_t *)(out + 1))[3] = DHCP_OPTION_IP_ADDRESS_LEASE_TIME;
+    out = (void *)((uint8_t *)(out + 1) + ntoh8(out->len));
+    
+    // end option
+    *((uint8_t *)out) = 0xFF;
+    out = (void *)((uint8_t *)out + 1);
+    
+    // send it
+    PacketPassInterface_Sender_Send(o->send_if, (uint8_t *)o->send_buf, (uint8_t *)out - (uint8_t *)o->send_buf);
+    o->sending = 1;
+}
+
+static void send_handler_done (BDHCPClientCore *o)
+{
+    ASSERT(o->sending)
+    DebugObject_Access(&o->d_obj);
+    
+    o->sending = 0;
+}
+
+static void recv_handler_done (BDHCPClientCore *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    // receive more packets
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf);
+    
+    if (o->state == STATE_RESETTING) {
+        return;
+    }
+    
+    // check header
+    
+    if (data_len < sizeof(*o->recv_buf)) {
+        return;
+    }
+    
+    if (ntoh8(o->recv_buf->op) != DHCP_OP_BOOTREPLY) {
+        return;
+    }
+    
+    if (ntoh8(o->recv_buf->htype) != DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET) {
+        return;
+    }
+    
+    if (ntoh8(o->recv_buf->hlen) != 6) {
+        return;
+    }
+    
+    if (o->recv_buf->xid != o->xid) {
+        return;
+    }
+    
+    if (memcmp(o->recv_buf->chaddr, o->client_mac_addr, sizeof(o->client_mac_addr))) {
+        return;
+    }
+    
+    if (ntoh32(o->recv_buf->magic) != DHCP_MAGIC) {
+        return;
+    }
+    
+    // parse and check options
+    
+    uint8_t *pos = (uint8_t *)o->recv_buf + sizeof(*o->recv_buf);
+    int len = data_len - sizeof(*o->recv_buf);
+    
+    int have_end = 0;
+    
+    int dhcp_message_type = -1;
+    
+    int have_dhcp_server_identifier = 0;
+    uint32_t dhcp_server_identifier;
+    
+    int have_ip_address_lease_time = 0;
+    uint32_t ip_address_lease_time;
+    
+    int have_subnet_mask = 0;
+    uint32_t subnet_mask;
+    
+    int have_router = 0;
+    uint32_t router;
+    
+    int domain_name_servers_count = 0;
+    uint32_t domain_name_servers[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS];
+    
+    while (len > 0) {
+        // padding option ?
+        if (*pos == 0) {
+            pos++;
+            len--;
+            continue;
+        }
+        
+        if (have_end) {
+            return;
+        }
+        
+        // end option ?
+        if (*pos == 0xff) {
+            pos++;
+            len--;
+            have_end = 1;
+            continue;
+        }
+        
+        // check option header
+        if (len < sizeof(struct dhcp_option_header)) {
+            return;
+        }
+        struct dhcp_option_header *opt = (void *)pos;
+        pos += sizeof(*opt);
+        len -= sizeof(*opt);
+        int opt_type = ntoh8(opt->type);
+        int opt_len = ntoh8(opt->len);
+        
+        // check option payload
+        if (opt_len > len) {
+            return;
+        }
+        void *optval = pos;
+        pos += opt_len;
+        len -= opt_len;
+        
+        switch (opt_type) {
+            case DHCP_OPTION_DHCP_MESSAGE_TYPE: {
+                if (opt_len != sizeof(struct dhcp_option_dhcp_message_type)) {
+                    return;
+                }
+                struct dhcp_option_dhcp_message_type *val = optval;
+                
+                dhcp_message_type = ntoh8(val->type);
+            } break;
+            
+            case DHCP_OPTION_DHCP_SERVER_IDENTIFIER: {
+                if (opt_len != sizeof(struct dhcp_option_dhcp_server_identifier)) {
+                    return;
+                }
+                struct dhcp_option_dhcp_server_identifier *val = optval;
+                
+                dhcp_server_identifier = val->id;
+                have_dhcp_server_identifier = 1;
+            } break;
+            
+            case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: {
+                if (opt_len != sizeof(struct dhcp_option_time)) {
+                    return;
+                }
+                struct dhcp_option_time *val = optval;
+                
+                ip_address_lease_time = ntoh32(val->time);
+                have_ip_address_lease_time = 1;
+            } break;
+            
+            case DHCP_OPTION_SUBNET_MASK: {
+                if (opt_len != sizeof(struct dhcp_option_addr)) {
+                    return;
+                }
+                struct dhcp_option_addr *val = optval;
+                
+                subnet_mask = val->addr;
+                have_subnet_mask = 1;
+            } break;
+            
+            case DHCP_OPTION_ROUTER: {
+                if (opt_len != sizeof(struct dhcp_option_addr)) {
+                    return;
+                }
+                struct dhcp_option_addr *val = optval;
+                
+                router = val->addr;
+                have_router = 1;
+            } break;
+            
+            case DHCP_OPTION_DOMAIN_NAME_SERVER: {
+                if (opt_len % 4) {
+                    return;
+                }
+                
+                int num_servers = opt_len / 4;
+                
+                int i;
+                for (i = 0; i < num_servers && i < BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS; i++) {
+                    domain_name_servers[i] = ((struct dhcp_option_addr *)optval + i)->addr;
+                }
+                
+                domain_name_servers_count = i;
+            } break;
+        }
+    }
+    
+    if (!have_end) {
+        return;
+    }
+    
+    if (dhcp_message_type == -1) {
+        return;
+    }
+    
+    if (dhcp_message_type != DHCP_MESSAGE_TYPE_OFFER && dhcp_message_type != DHCP_MESSAGE_TYPE_ACK && dhcp_message_type != DHCP_MESSAGE_TYPE_NAK) {
+        return;
+    }
+    
+    if (!have_dhcp_server_identifier) {
+        return;
+    }
+    
+    if (dhcp_message_type == DHCP_MESSAGE_TYPE_NAK) {
+        if (o->state != STATE_SENT_REQUEST && o->state != STATE_FINISHED && o->state != STATE_RENEWING) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        if (o->state == STATE_SENT_REQUEST) {
+            // stop request timer
+            BReactor_RemoveTimer(o->reactor, &o->request_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+        }
+        else if (o->state == STATE_FINISHED) {
+            // stop renew timer
+            BReactor_RemoveTimer(o->reactor, &o->renew_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+            
+            // report to user
+            report_down(o);
+            return;
+        }
+        else { // STATE_RENEWING
+            // stop renew request timer
+            BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+            
+            // stop lease timer
+            BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+            
+            // report to user
+            report_down(o);
+            return;
+        }
+        
+        return;
+    }
+    
+    if (ntoh32(o->recv_buf->yiaddr) == 0) {
+        return;
+    }
+    
+    if (!have_ip_address_lease_time) {
+        return;
+    }
+    
+    if (!have_subnet_mask) {
+        return;
+    }
+    
+    if (o->state == STATE_SENT_DISCOVER && dhcp_message_type == DHCP_MESSAGE_TYPE_OFFER) {
+        // remember offer
+        o->offered.yiaddr = o->recv_buf->yiaddr;
+        o->offered.dhcp_server_identifier = dhcp_server_identifier;
+        
+        // send request
+        send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier);
+        
+        // stop reset timer
+        BReactor_RemoveTimer(o->reactor, &o->reset_timer);
+        
+        // start request timer
+        BReactor_SetTimer(o->reactor, &o->request_timer);
+        
+        // set state
+        o->state = STATE_SENT_REQUEST;
+        
+        // set request count
+        o->request_count = 1;
+    }
+    else if (o->state == STATE_SENT_REQUEST && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) {
+        if (o->recv_buf->yiaddr != o->offered.yiaddr) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        // remember stuff
+        o->acked.ip_address_lease_time = ip_address_lease_time;
+        o->acked.subnet_mask = subnet_mask;
+        o->acked.have_router = have_router;
+        if (have_router) {
+            o->acked.router = router;
+        }
+        o->acked.domain_name_servers_count = domain_name_servers_count;
+        memcpy(o->acked.domain_name_servers, domain_name_servers, domain_name_servers_count * sizeof(uint32_t));
+        
+        // stop request timer
+        BReactor_RemoveTimer(o->reactor, &o->request_timer);
+        
+        // start renew timer
+        BReactor_SetTimerAfter(o->reactor, &o->renew_timer, 500 * o->acked.ip_address_lease_time);
+        
+        // set state
+        o->state = STATE_FINISHED;
+        
+        // report to user
+        report_up(o);
+        return;
+    }
+    else if (o->state == STATE_RENEWING && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) {
+        if (o->recv_buf->yiaddr != o->offered.yiaddr) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        // TODO: check parameters?
+        
+        // remember stuff
+        o->acked.ip_address_lease_time = ip_address_lease_time;
+        
+        // stop renew request timer
+        BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+        
+        // stop lease timer
+        BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+        
+        // start renew timer
+        BReactor_SetTimerAfter(o->reactor, &o->renew_timer, 500 * o->acked.ip_address_lease_time);
+        
+        // set state
+        o->state = STATE_FINISHED;
+    }
+}
+
+static void start_process (BDHCPClientCore *o)
+{
+    // generate xid
+    BRandom_randomize((uint8_t *)&o->xid, sizeof(o->xid));
+    
+    // send discover
+    send_message(o, DHCP_MESSAGE_TYPE_DISCOVER, o->xid, 0, 0, 0, 0);
+    
+    // set timer
+    BReactor_SetTimer(o->reactor, &o->reset_timer);
+    
+    // set state
+    o->state = STATE_SENT_DISCOVER;
+}
+
+static void reset_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RESETTING || o->state == STATE_SENT_DISCOVER)
+    DebugObject_Access(&o->d_obj);
+    
+    start_process(o);
+}
+
+static void request_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_SENT_REQUEST)
+    ASSERT(o->request_count >= 1)
+    ASSERT(o->request_count <= MAX_REQUESTS)
+    DebugObject_Access(&o->d_obj);
+    
+    // if we have sent enough requests, start again
+    if (o->request_count == MAX_REQUESTS) {
+        start_process(o);
+        return;
+    }
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier);
+    
+    // start request timer
+    BReactor_SetTimer(o->reactor, &o->request_timer);
+    
+    // increment request count
+    o->request_count++;
+}
+
+static void renew_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_FINISHED)
+    DebugObject_Access(&o->d_obj);
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0);
+    
+    // start renew request timer
+    BReactor_SetTimer(o->reactor, &o->renew_request_timer);
+    
+    // start lease timer
+    BReactor_SetTimerAfter(o->reactor, &o->lease_timer, 500 * o->acked.ip_address_lease_time);
+    
+    // set state
+    o->state = STATE_RENEWING;
+}
+
+static void renew_request_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0);
+    
+    // start renew request timer
+    BReactor_SetTimer(o->reactor, &o->renew_request_timer);
+}
+
+static void lease_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    // stop renew request timer
+    BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+    
+    // start again now
+    start_process(o);
+    
+    // report to user
+    report_down(o);
+    return;
+}
+
+int BDHCPClientCore_Init (BDHCPClientCore *o, PacketPassInterface *send_if, PacketRecvInterface *recv_if, uint8_t *client_mac_addr, BReactor *reactor, BDHCPClientCore_handler handler, void *user)
+{
+    ASSERT(PacketPassInterface_GetMTU(send_if) == PacketRecvInterface_GetMTU(recv_if))
+    ASSERT(PacketPassInterface_GetMTU(send_if) >= 576 - IP_UDP_HEADERS_SIZE)
+    
+    // init arguments
+    o->send_if = send_if;
+    o->recv_if = recv_if;
+    memcpy(o->client_mac_addr, client_mac_addr, sizeof(o->client_mac_addr));
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    
+    // allocate buffers
+    if (!(o->send_buf = malloc(PacketPassInterface_GetMTU(send_if)))) {
+        goto fail0;
+    }
+    if (!(o->recv_buf = malloc(PacketRecvInterface_GetMTU(recv_if)))) {
+        goto fail1;
+    }
+    
+    // init send interface
+    PacketPassInterface_Sender_Init(o->send_if, (PacketPassInterface_handler_done)send_handler_done, o);
+    
+    // init receive interface
+    PacketRecvInterface_Receiver_Init(o->recv_if, (PacketRecvInterface_handler_done)recv_handler_done, o);
+    
+    // set not sending
+    o->sending = 0;
+    
+    // init timers
+    BTimer_Init(&o->reset_timer, RESET_TIMEOUT, (BTimer_handler)reset_timer_handler, o);
+    BTimer_Init(&o->request_timer, REQUEST_TIMEOUT, (BTimer_handler)request_timer_handler, o);
+    BTimer_Init(&o->renew_timer, 0, (BTimer_handler)renew_timer_handler, o);
+    BTimer_Init(&o->renew_request_timer, RENEW_REQUEST_TIMEOUT, (BTimer_handler)renew_request_timer_handler, o);
+    BTimer_Init(&o->lease_timer, 0, (BTimer_handler)lease_timer_handler, o);
+    
+    // start receving
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf);
+    
+    // start
+    start_process(o);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    free(o->send_buf);
+fail0:
+    return 0;
+}
+
+void BDHCPClientCore_Free (BDHCPClientCore *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free timers
+    BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+    BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+    BReactor_RemoveTimer(o->reactor, &o->renew_timer);
+    BReactor_RemoveTimer(o->reactor, &o->request_timer);
+    BReactor_RemoveTimer(o->reactor, &o->reset_timer);
+    
+    // free buffers
+    free(o->recv_buf);
+    free(o->send_buf);
+}
+
+void BDHCPClientCore_GetClientIP (BDHCPClientCore *o, uint32_t *out_ip)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    *out_ip = o->offered.yiaddr;
+}
+
+void BDHCPClientCore_GetClientMask (BDHCPClientCore *o, uint32_t *out_mask)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    *out_mask = o->acked.subnet_mask;
+}
+
+int BDHCPClientCore_GetRouter (BDHCPClientCore *o, uint32_t *out_router)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->acked.have_router) {
+        return 0;
+    }
+    
+    *out_router = o->acked.router;
+    return 1;
+}
+
+int BDHCPClientCore_GetDNS (BDHCPClientCore *o, uint32_t *out_dns_servers, size_t max_dns_servers)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    int num_return = BMIN(o->acked.domain_name_servers_count, max_dns_servers);
+    
+    memcpy(out_dns_servers, o->acked.domain_name_servers, num_return * sizeof(uint32_t));
+    return num_return;
+}

+ 86 - 0
dhcpclient/BDHCPClientCore.h

@@ -0,0 +1,86 @@
+/**
+ * @file BDHCPClientCore.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * DHCP client, without I/O details.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_BDHCPCLIENTCORE_H
+#define BADVPN_DHCPCLIENT_BDHCPCLIENTCORE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/dhcp_proto.h>
+#include <system/BReactor.h>
+#include <system/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+#define BDHCPCLIENTCORE_EVENT_UP 1
+#define BDHCPCLIENTCORE_EVENT_DOWN 2
+
+#define BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS 16
+
+typedef void (*BDHCPClientCore_handler) (void *user, int event);
+
+typedef struct {
+    PacketPassInterface *send_if;
+    PacketRecvInterface *recv_if;
+    uint8_t client_mac_addr[6];
+    BReactor *reactor;
+    BDHCPClientCore_handler handler;
+    void *user;
+    struct dhcp_header *send_buf;
+    struct dhcp_header *recv_buf;
+    int sending;
+    BTimer reset_timer;
+    BTimer request_timer;
+    BTimer renew_timer;
+    BTimer renew_request_timer;
+    BTimer lease_timer;
+    int state;
+    int request_count;
+    uint32_t xid;
+    struct {
+        uint32_t yiaddr;
+        uint32_t dhcp_server_identifier;
+    } offered;
+    struct {
+        uint32_t ip_address_lease_time;
+        uint32_t subnet_mask;
+        int have_router;
+        uint32_t router;
+        int domain_name_servers_count;
+        uint32_t domain_name_servers[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS];
+    } acked;
+    DebugObject d_obj;
+} BDHCPClientCore;
+
+int BDHCPClientCore_Init (BDHCPClientCore *o, PacketPassInterface *send_if, PacketRecvInterface *recv_if, uint8_t *client_mac_addr, BReactor *reactor, BDHCPClientCore_handler handler, void *user);
+void BDHCPClientCore_Free (BDHCPClientCore *o);
+void BDHCPClientCore_GetClientIP (BDHCPClientCore *o, uint32_t *out_ip);
+void BDHCPClientCore_GetClientMask (BDHCPClientCore *o, uint32_t *out_mask);
+int BDHCPClientCore_GetRouter (BDHCPClientCore *o, uint32_t *out_router);
+int BDHCPClientCore_GetDNS (BDHCPClientCore *o, uint32_t *out_dns_servers, size_t max_dns_servers);
+
+#endif

+ 11 - 0
dhcpclient/CMakeLists.txt

@@ -0,0 +1,11 @@
+add_library(dhcpclientcore
+    BDHCPClientCore.c
+)
+target_link_libraries(dhcpclientcore system flow security)
+
+add_library(dhcpclient
+    BDHCPClient.c
+    DHCPIpUdpEncoder.c
+    DHCPIpUdpDecoder.c
+)
+target_link_libraries(dhcpclient system flow dhcpclientcore)

+ 124 - 0
dhcpclient/DHCPIpUdpDecoder.c

@@ -0,0 +1,124 @@
+/**
+ * @file DHCPIpUdpDecoder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <limits.h>
+
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/byteorder.h>
+
+#include <dhcpclient/DHCPIpUdpDecoder.h>
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+struct combined_header {
+    struct ipv4_header ip;
+    struct udp_header udp;
+} __attribute__((packed));
+
+static void input_handler_send (DHCPIpUdpDecoder *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    struct ipv4_header *iph;
+    uint8_t *pl;
+    int pl_len;
+    
+    if (!ipv4_check(data, data_len, &iph, &pl, &pl_len)) {
+        DEBUG("ipv4_check failed");
+        return;
+    }
+    
+    if (ntoh8(iph->protocol) != IPV4_PROTOCOL_UDP) {
+        DEBUG("not UDP");
+        return;
+    }
+    
+    if (pl_len < sizeof(struct udp_header)) {
+        DEBUG("no UDP header");
+        return;
+    }
+    struct udp_header *udph = (void *)pl;
+    
+    if (ntoh16(udph->source_port) != DHCP_SERVER_PORT) {
+        DEBUG("wrong source port");
+        return;
+    }
+    
+    if (ntoh16(udph->dest_port) != DHCP_CLIENT_PORT) {
+        DEBUG("wrong dest port");
+        return;
+    }
+    
+    int udph_length = ntoh16(udph->length);
+    if (udph_length < sizeof(*udph)) {
+        DEBUG("udp length too small");
+        return;
+    }
+    if (udph_length > data_len - (pl - data)) {
+        DEBUG("udp length too big");
+        return;
+    }
+    
+    // pass payload to output
+    PacketPassInterface_Sender_Send(o->output, (uint8_t *)(udph + 1), udph_length - sizeof(*udph));
+}
+
+static void output_handler_done (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketPassInterface_Done(&o->input);
+}
+
+void DHCPIpUdpDecoder_Init (DHCPIpUdpDecoder *o, PacketPassInterface *output, BPendingGroup *pg)
+{
+    ASSERT(PacketPassInterface_GetMTU(output) <= INT_MAX - sizeof(struct combined_header))
+    
+    // init arguments
+    o->output = output;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init input
+    PacketPassInterface_Init(&o->input, sizeof(struct combined_header) + PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DHCPIpUdpDecoder_Free (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * DHCPIpUdpDecoder_GetInput (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}

+ 42 - 0
dhcpclient/DHCPIpUdpDecoder.h

@@ -0,0 +1,42 @@
+/**
+ * @file DHCPIpUdpDecoder.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_DHCPIPUDPDECODER_H
+#define BADVPN_DHCPCLIENT_DHCPIPUDPDECODER_H
+
+#include <stdint.h>
+
+#include <system/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    PacketPassInterface *output;
+    PacketPassInterface input;
+    uint8_t *data;
+    DebugObject d_obj;
+} DHCPIpUdpDecoder;
+
+void DHCPIpUdpDecoder_Init (DHCPIpUdpDecoder *o, PacketPassInterface *output, BPendingGroup *pg);
+void DHCPIpUdpDecoder_Free (DHCPIpUdpDecoder *o);
+PacketPassInterface * DHCPIpUdpDecoder_GetInput (DHCPIpUdpDecoder *o);
+
+#endif

+ 113 - 0
dhcpclient/DHCPIpUdpEncoder.c

@@ -0,0 +1,113 @@
+/**
+ * @file DHCPIpUdpEncoder.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <limits.h>
+
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/byteorder.h>
+
+#include <dhcpclient/DHCPIpUdpEncoder.h>
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+struct combined_header {
+    struct ipv4_header ip;
+    struct udp_header udp;
+} __attribute__((packed));
+
+static void output_handler_recv (DHCPIpUdpEncoder *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // remember output packet
+    o->data = data;
+    
+    // receive payload
+    PacketRecvInterface_Receiver_Recv(o->input, o->data + sizeof(struct combined_header));
+}
+
+static void input_handler_done (DHCPIpUdpEncoder *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    struct combined_header *header = (void *)o->data;
+    
+    // write IP header
+    struct ipv4_header *iph = &header->ip;
+    iph->version4_ihl4 = IPV4_MAKE_VERSION_IHL(sizeof(*iph));
+    iph->ds = hton8(0);
+    iph->total_length = hton16(sizeof(struct combined_header) + data_len);
+    iph->identification = hton16(0);
+    iph->flags3_fragmentoffset13 = hton16(0);
+    iph->ttl = hton8(64);
+    iph->protocol = hton8(IPV4_PROTOCOL_UDP);
+    iph->checksum = hton16(0);
+    iph->source_address = hton32(0x00000000);
+    iph->destination_address = hton32(0xFFFFFFFF);
+    
+    // compute and write IP header checksum
+    uint32_t checksum = ipv4_checksum((uint8_t *)iph, sizeof(*iph));
+    iph->checksum = checksum;
+    
+    // write UDP header
+    struct udp_header *udph = &header->udp;
+    udph->source_port = hton16(DHCP_CLIENT_PORT);
+    udph->dest_port = hton16(DHCP_SERVER_PORT);
+    udph->length = hton16(sizeof(*udph) + data_len);
+    udph->checksum = hton16(0);
+    
+    // finish packet
+    PacketRecvInterface_Done(&o->output, sizeof(struct combined_header) + data_len);
+}
+
+void DHCPIpUdpEncoder_Init (DHCPIpUdpEncoder *o, PacketRecvInterface *input, BPendingGroup *pg)
+{
+    ASSERT(PacketRecvInterface_GetMTU(input) <= INT_MAX - sizeof(struct combined_header))
+    
+    // init arguments
+    o->input = input;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, sizeof(struct combined_header) + PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DHCPIpUdpEncoder_Free (DHCPIpUdpEncoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * DHCPIpUdpEncoder_GetOutput (DHCPIpUdpEncoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}

+ 42 - 0
dhcpclient/DHCPIpUdpEncoder.h

@@ -0,0 +1,42 @@
+/**
+ * @file DHCPIpUdpEncoder.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_DHCPIPUDPENCODER_H
+#define BADVPN_DHCPCLIENT_DHCPIPUDPENCODER_H
+
+#include <stdint.h>
+
+#include <system/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+typedef struct {
+    PacketRecvInterface *input;
+    PacketRecvInterface output;
+    uint8_t *data;
+    DebugObject d_obj;
+} DHCPIpUdpEncoder;
+
+void DHCPIpUdpEncoder_Init (DHCPIpUdpEncoder *o, PacketRecvInterface *input, BPendingGroup *pg);
+void DHCPIpUdpEncoder_Free (DHCPIpUdpEncoder *o);
+PacketRecvInterface * DHCPIpUdpEncoder_GetOutput (DHCPIpUdpEncoder *o);
+
+#endif

+ 3 - 0
examples/CMakeLists.txt

@@ -21,6 +21,9 @@ target_link_libraries(bavl_test security)
 add_executable(bencryption_bench bencryption_bench.c)
 target_link_libraries(bencryption_bench system security)
 
+add_executable(dhcpclient_test dhcpclient_test.c)
+target_link_libraries(dhcpclient_test dhcpclient)
+
 if (NOT WIN32)
     add_executable(ipc_server ipc_server.c)
     target_link_libraries(ipc_server ipc)

+ 157 - 0
examples/dhcpclient_test.c

@@ -0,0 +1,157 @@
+/**
+ * @file dhcpclient_test.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <system/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <dhcpclient/BDHCPClient.h>
+
+BReactor reactor;
+BDHCPClient dhcp;
+
+static void terminate (int ret);
+static void signal_handler (void *user);
+static void dhcp_handler (void *unused, int event);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    if (argc != 2) {
+        printf("Usage: %s <interface>\n", argv[0]);
+        goto fail0;
+    }
+    
+    char *ifname = argv[1];
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BSignal_Init()) {
+        DEBUG("BSignal_Init failed");
+        goto fail2;
+    }
+    
+    BSignal_Capture();
+    
+    if (!BSignal_SetHandler(&reactor, signal_handler, NULL)) {
+        DEBUG("BSignal_SetHandler failed");
+        goto fail2;
+    }
+    
+    if (!BDHCPClient_Init(&dhcp, ifname, &reactor, dhcp_handler, NULL)) {
+        DEBUG("BDHCPClient_Init failed");
+        goto fail3;
+    }
+    
+    int ret = BReactor_Exec(&reactor);
+    
+    BReactor_Free(&reactor);
+    
+    BLog_Free();
+    
+    DebugObjectGlobal_Finish();
+    
+    return ret;
+    
+fail3:
+    BSignal_RemoveHandler();
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    return 1;
+}
+
+void terminate (int ret)
+{
+    BDHCPClient_Free(&dhcp);
+    
+    BSignal_RemoveHandler();
+    
+    BReactor_Quit(&reactor, ret);
+}
+
+void signal_handler (void *user)
+{
+    DEBUG("termination requested");
+    
+    terminate(1);
+}
+
+void BDHCPClient_GetClientIP (BDHCPClient *o, uint32_t *out_ip);
+void BDHCPClient_GetClientMask (BDHCPClient *o, uint32_t *out_mask);
+int BDHCPClient_GetRouter (BDHCPClient *o, uint32_t *out_router);
+int BDHCPClient_GetDNS (BDHCPClient *o, uint32_t *out_dns_servers, size_t max_dns_servers);
+
+void dhcp_handler (void *unused, int event)
+{
+    switch (event) {
+        case BDHCPCLIENTCORE_EVENT_UP: {
+            printf("DHCP: up");
+            
+            uint32_t ip;
+            uint8_t *ipb = (void *)&ip;
+            
+            BDHCPClient_GetClientIP(&dhcp, &ip);
+            printf(" IP=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            
+            BDHCPClient_GetClientMask(&dhcp, &ip);
+            printf(" Mask=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            
+            if (BDHCPClient_GetRouter(&dhcp, &ip)) {
+                printf(" Router=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            }
+            
+            uint32_t dns[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS];
+            int num = BDHCPClient_GetDNS(&dhcp, dns, BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS);
+            for (int i = 0; i < num; i++) {
+                ip=dns[i];
+                printf(" DNS=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            }
+            
+            printf("\n");
+        } break;
+        
+        case BDHCPCLIENTCORE_EVENT_DOWN: {
+            printf("DHCP: down\n");
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}