Skip to content

File syn_dhcp.c

File List > net > syn_dhcp.c

Go to the documentation of this file

#include "syntropic/net/syn_dhcp.h"

#include <string.h>

SYN_Status syn_dhcp_init(SYN_DHCP *dhcp, uint32_t xid)
{
    if (!dhcp) {
        return SYN_INVALID_PARAM;
    }
    memset(dhcp, 0, sizeof(*dhcp));
    dhcp->state = SYN_DHCP_STATE_INIT;
    dhcp->xid = (xid != 0) ? xid : 0x12345678UL;
    return SYN_OK;
}

static void pack_uint32(uint8_t *buf, uint32_t val)
{
    buf[0] = (uint8_t)(val >> 24);
    buf[1] = (uint8_t)(val >> 16);
    buf[2] = (uint8_t)(val >> 8);
    buf[3] = (uint8_t)(val);
}

static uint32_t unpack_uint32(const uint8_t *buf)
{
    return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) |
           (uint32_t)buf[3];
}

SYN_Status syn_dhcp_build_discover(SYN_DHCP *dhcp, const uint8_t mac_addr[6], uint8_t *buf_out,
                                   size_t buf_size, size_t *len_out)
{
    if (!dhcp || !mac_addr || !buf_out || buf_size < 250U || !len_out) {
        return SYN_INVALID_PARAM;
    }

    memset(buf_out, 0, 250);
    buf_out[0] = 1; /* op = Boot Request */
    buf_out[1] = 1; /* htype = Ethernet */
    buf_out[2] = 6; /* hlen = 6 */
    buf_out[3] = 0; /* hops = 0 */

    pack_uint32(&buf_out[4], dhcp->xid);
    memcpy(&buf_out[28], mac_addr, 6); /* chaddr */

    /* Magic Cookie: 0x63, 0x82, 0x53, 0x63 */
    pack_uint32(&buf_out[236], SYN_DHCP_MAGIC_COOKIE);

    /* Options: Option 53 = DHCPDISCOVER (1), Option 255 = END */
    size_t opt_idx = 240;
    buf_out[opt_idx++] = SYN_DHCP_OPT_MSG_TYPE;
    buf_out[opt_idx++] = 1;
    buf_out[opt_idx++] = SYN_DHCP_DISCOVER;
    buf_out[opt_idx++] = SYN_DHCP_OPT_END;

    dhcp->state = SYN_DHCP_STATE_DISCOVER;
    dhcp->discovers_sent++;
    *len_out = opt_idx;

    return SYN_OK;
}

SYN_Status syn_dhcp_build_request(SYN_DHCP *dhcp, const uint8_t mac_addr[6], uint8_t *buf_out,
                                  size_t buf_size, size_t *len_out)
{
    if (!dhcp || !mac_addr || !buf_out || buf_size < 260U || !len_out) {
        return SYN_INVALID_PARAM;
    }

    memset(buf_out, 0, 260);
    buf_out[0] = 1; /* op = Boot Request */
    buf_out[1] = 1; /* htype = Ethernet */
    buf_out[2] = 6; /* hlen = 6 */
    buf_out[3] = 0;

    pack_uint32(&buf_out[4], dhcp->xid);
    memcpy(&buf_out[28], mac_addr, 6);

    /* Magic Cookie */
    pack_uint32(&buf_out[236], SYN_DHCP_MAGIC_COOKIE);

    size_t opt_idx = 240;
    /* Option 53 = DHCPREQUEST (3) */
    buf_out[opt_idx++] = SYN_DHCP_OPT_MSG_TYPE;
    buf_out[opt_idx++] = 1;
    buf_out[opt_idx++] = SYN_DHCP_REQUEST;

    /* Option 50 = Requested IP Address */
    buf_out[opt_idx++] = 50;
    buf_out[opt_idx++] = 4;
    pack_uint32(&buf_out[opt_idx], dhcp->offered_ip);
    opt_idx += 4;

    /* Option 54 = Server Identifier */
    if (dhcp->server_ip != 0) {
        buf_out[opt_idx++] = 54;
        buf_out[opt_idx++] = 4;
        pack_uint32(&buf_out[opt_idx], dhcp->server_ip);
        opt_idx += 4;
    }

    buf_out[opt_idx++] = SYN_DHCP_OPT_END;

    dhcp->state = SYN_DHCP_STATE_REQUEST;
    dhcp->requests_sent++;
    *len_out = opt_idx;

    return SYN_OK;
}

SYN_Status syn_dhcp_process_packet(SYN_DHCP *dhcp, SYN_ETH *eth, const uint8_t *dhcp_pkt,
                                   size_t len)
{
    if (!dhcp || !dhcp_pkt || len < 244) {
        return SYN_INVALID_PARAM;
    }

    /* Verify Magic Cookie */
    if (unpack_uint32(&dhcp_pkt[236]) != SYN_DHCP_MAGIC_COOKIE) {
        return SYN_INVALID_PARAM;
    }

    /* Verify Transaction ID */
    if (unpack_uint32(&dhcp_pkt[4]) != dhcp->xid) {
        return SYN_INVALID_PARAM;
    }

    uint32_t yiaddr = unpack_uint32(&dhcp_pkt[16]);

    /* Parse Options */
    uint8_t msg_type = 0;
    uint32_t subnet = 0;
    uint32_t router = 0;
    uint32_t lease = 0;
    uint32_t server = 0;

    size_t i = 240;
    while (i < len && dhcp_pkt[i] != SYN_DHCP_OPT_END) {
        uint8_t opt = dhcp_pkt[i++];
        if (opt == 0)
            continue; /* Pad */
        if (i >= len)
            break;
        uint8_t opt_len = dhcp_pkt[i++];
        if (i + opt_len > len)
            break;

        if (opt == SYN_DHCP_OPT_MSG_TYPE && opt_len >= 1) {
            msg_type = dhcp_pkt[i];
        } else if (opt == SYN_DHCP_OPT_SUBNET_MASK && opt_len >= 4) {
            subnet = unpack_uint32(&dhcp_pkt[i]);
        } else if (opt == SYN_DHCP_OPT_ROUTER && opt_len >= 4) {
            router = unpack_uint32(&dhcp_pkt[i]);
        } else if (opt == SYN_DHCP_OPT_LEASE_TIME && opt_len >= 4) {
            lease = unpack_uint32(&dhcp_pkt[i]);
        } else if (opt == 54 && opt_len >= 4) {
            server = unpack_uint32(&dhcp_pkt[i]);
        }
        i += opt_len;
    }

    if (msg_type == SYN_DHCP_OFFER) {
        dhcp->offered_ip = yiaddr;
        dhcp->subnet_mask = subnet;
        dhcp->gateway = router;
        dhcp->lease_time_sec = lease;
        dhcp->server_ip = server;
        dhcp->state = SYN_DHCP_STATE_OFFER;
        return SYN_BUSY;
    } else if (msg_type == SYN_DHCP_ACK) {
        dhcp->offered_ip = yiaddr;
        dhcp->subnet_mask = subnet;
        dhcp->gateway = router;
        dhcp->lease_time_sec = lease;
        dhcp->state = SYN_DHCP_STATE_BOUND;
        dhcp->acks_received++;

        /* Update Ethernet Interface settings */
        if (eth) {
            eth->ip_addr = yiaddr;
            eth->netmask = subnet;
            eth->gateway = router;
        }

        return SYN_OK;
    }

    return SYN_BUSY;
}