aboutgitcodebugslistschat
path: root/dhcp.c
blob: 3af4ace27f85d5ed80f21d5e4fa995e38e64bdf0 (plain) (tree)
1
2
3
4
5
6
7
8

                                             
                                         
  
                                              
  
                                       
                                              









                           
                       



                      
                  

                 
                





























































































































































































                                                                             
                                                 



                                     
// SPDX-License-Identifier: AGPL-3.0-or-later

/* PASST - Plug A Simple Socket Transport
 *
 * dhcp.c - Minimalistic DHCP server for PASST
 *
 * Copyright (c) 2020-2021 Red Hat GmbH
 * Author: Stefano Brivio <sbrivio@redhat.com>
 *
 */

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>
#include <net/if.h>
#include <arpa/inet.h>

#include "passt.h"
#include "dhcp.h"
#include "util.h"
#include "tap.h"

/**
 * struct opt - DHCP option
 * @force:	Force sending, even if the client didn't request it
 * @sent:	Convenience flag, set while filling replies
 * @slen:	Length of option defined for server
 * @s:		Option payload from server
 * @clen:	Length of option received from client
 * @c:		Option payload from client
 */
struct opt {
	int sent;
	int slen;
	unsigned char s[255];
	int clen;
	unsigned char c[255];
};

static struct opt opts[255] = {
	[1]  = { 0, 4, {  0 }, 0, { 0 }, },	/* Subnet mask */
	[3]  = { 0, 4, {  0 }, 0, { 0 }, },	/* Router */
	[6]  = { 0, 4, {  0 }, 0, { 0 }, },	/* DNS */
	[51] = { 0, 4, { 60 }, 0, { 0 }, },	/* Lease time */
	[53] = { 0, 1, {  0 }, 0, { 0 }, },	/* Message type */
#define DHCPDISCOVER	1
#define DHCPOFFER	2
#define DHCPREQUEST	3
#define DHCPDECLINE	4
#define DHCPACK		5
#define DHCPNAK		6
#define DHCPRELEASE	7
#define DHCPINFORM	8
#define DHCPFORCERENEW	9
	[54] = { 0, 4, {  0 }, 0, { 0 }, },	/* Server ID */
};

/**
 * struct msg - BOOTP/DHCP message
 * @op:		BOOTP message type
 * @htype:	Hardware address type
 * @hlen:	Hardware address length
 * @hops:	DHCP relay hops
 * @xid:	Transaction ID randomly chosen by client
 * @secs:	Seconds elapsed since beginning of acquisition or renewal
 * @flags:	DHCP message flags
 * @ciaddr:	Client IP address in BOUND, RENEW, REBINDING
 * @yiaddr:	IP address being offered or assigned
 * @siaddr:	Next server to use in bootstrap
 * @giaddr:	Relay agent IP address
 * @chaddr:	Client hardware address
 * @sname:	Server host name
 * @file:	Boot file name
 * @magic:	Magic cookie prefix before options
 * @o:		Options
 */
struct msg {
	uint8_t op;
#define BOOTREQUEST	1
#define BOOTREPLY	2
	uint8_t htype;
	uint8_t hlen;
	uint8_t hops;
	uint32_t xid;
	uint16_t secs;
	uint16_t flags;
	uint32_t ciaddr;
	uint32_t yiaddr;
	uint32_t siaddr;
	uint32_t giaddr;
	uint8_t chaddr[16];
	uint8_t sname[64];
	uint8_t file[128];
	uint32_t magic;
	uint8_t o[308];
} __attribute__((__packed__));

/**
 * fill_one() - Fill a single option in message
 * @m:		Message to fill
 * @o:		Option number
 * @offset:	Current offset within options field, updated on insertion
 */
static void fill_one(struct msg *m, int o, int *offset)
{
	m->o[*offset] = o;
	m->o[*offset + 1] = opts[o].slen;
	memcpy(&m->o[*offset + 2], opts[o].s, opts[o].slen);

	opts[o].sent = 1;
	*offset += 2 + opts[o].slen;
}

/**
 * fill() - Fill requested and forced options in message
 * @m:		Message to fill
 *
 * Return: current size of options field
 */
static int fill(struct msg *m)
{
	int i, o, offset = 0;

	m->op = BOOTREPLY;
	m->secs = 0;

	for (o = 0; o < 255; o++)
		opts[o].sent = 0;

	for (i = 0; i < opts[55].clen; i++) {
		o = opts[55].c[i];
		if (opts[o].slen)
			fill_one(m, o, &offset);
	}

	for (o = 0; o < 255; o++) {
		if (opts[o].slen && !opts[o].sent)
			fill_one(m, o, &offset);
	}

	m->o[offset++] = 255;
	m->o[offset++] = 0;

	if (offset < 62 /* RFC 951 */) {
		memset(&m->o[offset], 0, 62 - offset);
		offset = 62;
	}

	return offset;
}

/**
 * dhcp() - Check if this is a DHCP message, reply as needed
 * @c:		Execution context
 * @len:	Total L2 packet length
 * @eh:		Packet buffer, Ethernet header
 *
 * Return: 0 if it's not a DHCP message, 1 if handled, -1 on failure
 */
int dhcp(struct ctx *c, unsigned len, struct ethhdr *eh)
{
	struct iphdr *iph = (struct iphdr *)(eh + 1);
	struct udphdr *uh = (struct udphdr *)((char *)iph + iph->ihl * 4);
	struct msg *m = (struct msg *)(uh + 1);
	unsigned int i, mlen = len - sizeof(*eh) - sizeof(*iph);

	if (uh->dest != htons(67))
		return 0;

	if (mlen != ntohs(uh->len) || mlen < offsetof(struct msg, o) ||
	    m->op != BOOTREQUEST)
		return -1;

	for (i = 0; i < mlen - offsetof(struct msg, o); i += m->o[i + 1] + 2)
		memcpy(&opts[m->o[i]].c, &m->o[i + 2], m->o[i + 1]);

	if (opts[53].c[0] == DHCPDISCOVER) {
		fprintf(stderr, "DHCP: offer to discover");
		opts[53].s[0] = DHCPOFFER;
	} else if (opts[53].c[0] == DHCPREQUEST) {
		fprintf(stderr, "DHCP: ack to request");
		opts[53].s[0] = DHCPACK;
	} else {
		return -1;
	}

	fprintf(stderr, " from %02x:%02x:%02x:%02x:%02x:%02x\n\n",
		m->chaddr[0], m->chaddr[1], m->chaddr[2],
		m->chaddr[3], m->chaddr[4], m->chaddr[5]);

	m->yiaddr = c->addr4;
	*(unsigned long *)opts[1].s =  c->mask4;
	*(unsigned long *)opts[3].s =  c->gw4;
	*(unsigned long *)opts[6].s =  c->dns4;
	*(unsigned long *)opts[54].s = c->gw4;

	uh->len = htons(len = offsetof(struct msg, o) + fill(m));
	uh->check = 0;
	uh->source = htons(67);
	uh->dest = htons(68);

	iph->tot_len = htons(len += sizeof(*iph));
	iph->daddr = c->addr4;
	iph->saddr = c->gw4;
	iph->check = 0;
	iph->check = csum_ip4(iph, iph->ihl * 4);

	len += sizeof(*eh);
	memcpy(eh->h_dest, eh->h_source, ETH_ALEN);
	memcpy(eh->h_source, c->mac, ETH_ALEN);

	if (tap_send(c->fd_unix, eh, len, 0) < 0)
		perror("DHCP: send");

	return 1;
}