Files
Astral/tools/netd/main.c
T

578 lines
14 KiB
C

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <poll.h>
#include <net/route.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <limits.h>
#ifndef SIOCADDRT
#define SIOCADDRT 0x890B
#endif
#ifndef SIOCSIFADDR
#define SIOCSIFADDR 0x8916
#endif
#define DHCPHDR_TYPE_CLIENT 1
#define DHCPHDR_TYPE_SERVER 2
#define DHCPHDR_HTYPE_ETH 1
#define DHCPHDR_HLEN_ETH 6
#define DHCPHDR_COOKIE 0x63825363
#define DHCP_CLIENT_PORT 68
#define DHCP_SERVER_PORT 67
typedef struct {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t clientip;
uint32_t responseip;
uint32_t serverip;
uint32_t relayip;
uint8_t clienthw[16];
uint8_t sname[64];
uint8_t file[128];
uint32_t cookie;
} __attribute__((packed)) dhcphdr_t;
#define OPTION_TYPE 53
#define OPTION_TYPE_LENGTH 1
#define OPTION_TYPE_DHCPDISCOVER 1
#define OPTION_TYPE_DHCPOFFER 2
#define OPTION_TYPE_DHCPREQUEST 3
#define OPTION_TYPE_DHCPACK 5
#define OPTION_REQUEST 55
#define OPTION_REQUEST_LENGTH 3
#define OPTION_CLIENT_ID 61
#define OPTION_CLIENT_ID_LENGTH 7
#define OPTION_HOSTNAME 12
#define OPTION_HOSTNAME_LENGTH 64
#define OPTION_MASK 1
#define OPTION_ROUTER 3
#define OPTION_DNS 6
#define OPTION_LEASETIME 51
#define OPTION_IPREQ 50
#define OPTION_SERVER 54
#define OPTION_END 0xff
#define DISCOVEROPTS_OFFSET_CLIENT_ID 11
#define DISCOVEROPTS_OFFSET_HOSTNAME 19
static uint8_t discoveropts[] = {
OPTION_TYPE, OPTION_TYPE_LENGTH, OPTION_TYPE_DHCPDISCOVER,
OPTION_REQUEST, OPTION_REQUEST_LENGTH, OPTION_MASK, OPTION_ROUTER, OPTION_DNS,
OPTION_CLIENT_ID, OPTION_CLIENT_ID_LENGTH, 1, 0, 0, 0, 0, 0, 0,
OPTION_HOSTNAME, OPTION_HOSTNAME_LENGTH,
// 64 bytes for hostname
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
OPTION_END
};
typedef struct {
dhcphdr_t hdr;
uint8_t opts[sizeof(discoveropts)];
} __attribute__((packed)) dhcpdiscover_t;
typedef struct {
dhcphdr_t hdr;
uint8_t opts[];
} __attribute__((packed)) dhcpoffer_t;
typedef struct {
uint8_t type[3];
uint8_t ipreqhdr[2];
uint32_t ipreq;
uint8_t dhcpserverhdr[2];
uint32_t dhcpserver;
uint8_t userid;
uint8_t useridlen;
uint8_t useridtype;
uint8_t useridhwaddr[6];
uint8_t hn;
uint8_t hnlen;
uint8_t hndata[64];
uint8_t end;
} __attribute__((packed)) requestopts_t;
typedef struct {
dhcphdr_t hdr;
requestopts_t opts;
} __attribute__((packed)) dhcprequest_t;
typedef struct {
dhcphdr_t hdr;
uint8_t opts[];
} __attribute__((packed)) dhcpack_t;
static char *name;
static int sockfd;
static uint8_t hwaddr[DHCPHDR_HLEN_ETH];
static uint32_t xid;
static time_t discoverstart;
static void usage() {
fprintf(stderr, "%s: usage: %s device\n", name, name);
fprintf(stderr, "%s: example: %s vionet0\n", name, name);
exit(EXIT_FAILURE);
}
static void logstrerror(char *msg) {
fprintf(stderr, "%s: %s: %s\n", name, msg, strerror(errno));
}
static void logmsg(char *msg) {
printf("%s: %s\n", name, msg);
}
static void logerr(char *msg) {
fprintf(stderr, "%s: %s\n", name, msg);
}
int createsocket(char *device) {
// initialize the DHCP socket and bind it to the client port
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
logstrerror("failed to create socket");
return 1;
}
struct sockaddr_in addr;
addr.sin_port = htons(DHCP_CLIENT_PORT);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in))) {
logstrerror("failed to bind socket");
return 1;
}
// make the socket able to broadcast datagrams and bind it to the chosen device
int broadcast = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) {
logstrerror("failed to make socket accept broadcasts");
return 1;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1)) {
logstrerror("failed to bind to device");
return 1;
}
return 0;
}
int getdevicehwaddr(char *device) {
// get hardware address of device
struct ifreq buffer = {0};
strcpy(buffer.ifr_name, device);
if (ioctl(sockfd, SIOCGIFHWADDR, &buffer)) {
logstrerror("failed to get device hardware address");
return 1;
}
memcpy(hwaddr, buffer.ifr_hwaddr.sa_data, DHCPHDR_HLEN_ETH);
char printbuff[50];
snprintf(printbuff, 50, "hardware address: %02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
logmsg(printbuff);
return 0;
}
static int discover() {
// send out the DHCPDISCOVER packet
dhcpdiscover_t packet;
memset(&packet, 0, sizeof(packet));
packet.hdr.op = DHCPHDR_TYPE_CLIENT;
packet.hdr.htype = DHCPHDR_HTYPE_ETH;
packet.hdr.hlen = DHCPHDR_HLEN_ETH;
packet.hdr.xid = htonl(xid);
packet.hdr.secs = htons((uint16_t)(time(NULL) - discoverstart));
packet.hdr.flags = htons(0x8000);
memcpy(packet.hdr.clienthw, hwaddr, DHCPHDR_HLEN_ETH);
packet.hdr.cookie = htonl(DHCPHDR_COOKIE);
memcpy(packet.opts, discoveropts, sizeof(packet.opts));
// broadcast address
struct sockaddr_in address = {
.sin_family = AF_INET,
.sin_port = htons(DHCP_SERVER_PORT),
.sin_addr.s_addr = htonl(INADDR_BROADCAST)
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&address, sizeof(address)) < 0) {
logstrerror("sending DHCPDISCOVER failed");
return 1;
}
return 0;
}
static int waitforserver(int timeout) {
struct pollfd pollfd = {
.fd = sockfd,
.events = POLLIN
};
int pollr = poll(&pollfd, 1, timeout);
return pollr <= 0 ? 1 : 0;
}
static inline uint32_t optget32(uint8_t *opt, int i) {
return ((uint32_t)opt[i] << 24) | ((uint32_t)opt[i + 1] << 16) | ((uint32_t)opt[i + 2] << 8) | ((uint32_t)opt[i + 3]);
}
typedef struct {
uint32_t clientip;
uint32_t serverip;
uint32_t netmask;
uint32_t router;
uint32_t leasetime;
uint32_t dns;
} offer_t;
static int processoffer(offer_t *offer, uint32_t *xid) {
uint8_t buffer[1024];
dhcpoffer_t *dhcpoffer = (dhcpoffer_t *)buffer;
memset(buffer, 0, sizeof(buffer));
size_t nbytes = recv(sockfd, buffer, sizeof(buffer), 0);
if (nbytes < 0) {
logstrerror("failed to recv while processing offer");
return 1;
}
// not from the server, ignore
if (dhcpoffer->hdr.op != DHCPHDR_TYPE_SERVER) {
*xid = 0;
return 0;
}
size_t optsize = nbytes - sizeof(dhcphdr_t);
// get the offered stuff in the options
int i = 0;
uint8_t *opts = dhcpoffer->opts;
while (i < optsize) {
switch (opts[i]) {
case OPTION_TYPE:
if (opts[i + 2] != OPTION_TYPE_DHCPOFFER) {
// not a DHCPOFFER, ignore
*xid = 0;
return 0;
}
break;
case OPTION_MASK:
uint32_t mask = optget32(opts, i + 2);
offer->netmask = mask;
break;
case OPTION_ROUTER :
uint32_t router = optget32(opts, i + 2);
offer->router = router;
break;
case OPTION_SERVER:
uint32_t server = optget32(opts, i + 2);
offer->serverip = server;
break;
case OPTION_LEASETIME:
uint32_t leasetime = optget32(opts, i + 2);
offer->leasetime = leasetime;
break;
case OPTION_DNS:
uint32_t dns = optget32(opts, i + 2);
offer->dns = dns;
break;
case OPTION_END:
goto leave;
}
uint8_t optlen = opts[i + 1];
i += optlen + 2;
}
leave:
offer->clientip = ntohl(dhcpoffer->hdr.responseip);
*xid = ntohl(dhcpoffer->hdr.xid);
return 0;
}
static int request(offer_t *offer) {
// send out the DHCPREQUEST packet
dhcprequest_t packet;
memset(&packet, 0, sizeof(packet));
packet.hdr.op = DHCPHDR_TYPE_CLIENT;
packet.hdr.htype = DHCPHDR_HTYPE_ETH;
packet.hdr.hlen = DHCPHDR_HLEN_ETH;
packet.hdr.xid = htonl(xid);
packet.hdr.secs = htons((uint16_t)(time(NULL) - discoverstart));
packet.hdr.flags = htons(0x8000);
memcpy(packet.hdr.clienthw, hwaddr, DHCPHDR_HLEN_ETH);
packet.hdr.cookie = htonl(DHCPHDR_COOKIE);
packet.opts.type[0] = OPTION_TYPE;
packet.opts.type[1] = OPTION_TYPE_LENGTH;
packet.opts.type[2] = OPTION_TYPE_DHCPREQUEST;
packet.opts.ipreqhdr[0] = OPTION_IPREQ;
packet.opts.ipreqhdr[1] = 4;
packet.opts.ipreq = htonl(offer->clientip);
packet.opts.dhcpserverhdr[0] = OPTION_SERVER;
packet.opts.dhcpserverhdr[1] = 4;
packet.opts.dhcpserver = htonl(offer->serverip);
packet.opts.userid = OPTION_CLIENT_ID;
packet.opts.useridlen = OPTION_CLIENT_ID_LENGTH;
packet.opts.useridtype = 1;
memcpy(packet.opts.useridhwaddr, hwaddr, 6);
packet.opts.hn = OPTION_HOSTNAME;
gethostname(packet.opts.hndata, 64);
packet.opts.hnlen = packet.opts.hndata[63] ? 64 : strlen(packet.opts.hndata);
packet.opts.end = 0xff;
// broadcast address
struct sockaddr_in address = {
.sin_family = AF_INET,
.sin_port = htons(DHCP_SERVER_PORT),
.sin_addr.s_addr = htonl(INADDR_BROADCAST)
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&address, sizeof(address)) < 0) {
logstrerror("sending DHCPREQUEST failed");
return 1;
}
return 0;
}
static int getack(uint32_t *xid) {
uint8_t buffer[1024];
dhcpoffer_t *dhcpack = (dhcpoffer_t *)buffer;
memset(buffer, 0, sizeof(buffer));
size_t nbytes = recv(sockfd, buffer, sizeof(buffer), 0);
if (nbytes < 0) {
logstrerror("failed to recv while processing ack");
return 1;
}
// not from the server, ignore
if (dhcpack->hdr.op != DHCPHDR_TYPE_SERVER) {
*xid = 0;
return 0;
}
size_t optsize = nbytes - sizeof(dhcphdr_t);
// get the offered stuff in the options
int i = 0;
uint8_t *opts = dhcpack->opts;
while (i < optsize) {
switch (opts[i]) {
case OPTION_TYPE:
if (opts[i + 2] == OPTION_TYPE_DHCPACK) {
*xid = ntohl(dhcpack->hdr.xid);
return 0;
}
break;
case OPTION_END:
goto leave;
}
uint8_t optlen = opts[i + 1];
i += optlen + 2;
}
leave:
// if we reach this, its not an ack packet.
*xid = 0;
return 0;
}
static void printip(char *ipname, uint32_t ip) {
struct in_addr addr;
addr.s_addr = htonl(ip);
char *ipstr = inet_ntoa(addr);
printf("%s: %s: %s\n", name, ipname, ipstr);
}
static void printoffer(offer_t *offer) {
printip("client ip", offer->clientip);
printip("server ip", offer->serverip);
printip("netmask", offer->netmask);
printip("router", offer->router);
printip("DNS", offer->dns);
printf("%s: lease time: %d seconds\n", name, offer->leasetime);
}
static void addroute(char *device, uint32_t addr, uint32_t gateway, uint32_t mask, int weight) {
struct rtentry route;
memset(&route, 0, sizeof(route));
struct sockaddr_in sockaddr;
struct sockaddr_in sockgateway;
struct sockaddr_in sockmask;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = htonl(addr);
sockgateway.sin_family = AF_INET;
sockgateway.sin_addr.s_addr = htonl(gateway);
sockmask.sin_family = AF_INET;
sockmask.sin_addr.s_addr = htonl(mask);
memcpy(&route.rt_dst, &sockaddr, sizeof(struct sockaddr_in));
memcpy(&route.rt_gateway, &sockgateway, sizeof(struct sockaddr_in));
memcpy(&route.rt_genmask, &sockmask, sizeof(struct sockaddr_in));
route.rt_metric = weight;
route.rt_dev = device;
if (ioctl(sockfd, SIOCADDRT, &route) < 0)
logstrerror("failed to add route");
}
static void setifip(char *device, uint32_t addr) {
// get hardware address of device
struct ifreq buffer = {0};
strcpy(buffer.ifr_name, device);
struct sockaddr_in *sockaddr = (struct sockaddr_in *)&buffer.ifr_addr;
sockaddr->sin_family = AF_INET;
sockaddr->sin_addr.s_addr = htonl(addr);
if (ioctl(sockfd, SIOCSIFADDR, &buffer))
logstrerror("failed to set device ip address");
}
static void writedns(uint32_t dns) {
char buff[100];
int fd = creat("/etc/resolv.conf", 0644);
if (fd < 0) {
perror("failed to create /etc/resolv.conf");
return;
}
struct in_addr addr;
addr.s_addr = htonl(dns);
char *ipstr = inet_ntoa(addr);
snprintf(buff, 100, "# generated by netd\nnameserver %s\n", ipstr);
if (write(fd, buff, strlen(buff)) < 0) {
perror("failed to write to /etc/resolv.conf");
return;
}
close(fd);
}
int main(int argc, char *argv[]) {
name = argv[0];
if (argc != 2)
usage();
if (createsocket(argv[1]))
return EXIT_FAILURE;
if (getdevicehwaddr(argv[1]))
return EXIT_FAILURE;
srand(time(NULL));
xid = ((uint32_t)rand() << 16) ^ (uint32_t)rand();
discoverstart = time(NULL);
memcpy(&discoveropts[DISCOVEROPTS_OFFSET_CLIENT_ID], hwaddr, 6);
gethostname(&discoveropts[DISCOVEROPTS_OFFSET_HOSTNAME], 64);
if (discoveropts[DISCOVEROPTS_OFFSET_HOSTNAME + 63] == '\0')
discoveropts[DISCOVEROPTS_OFFSET_HOSTNAME - 1] = strlen(&discoveropts[DISCOVEROPTS_OFFSET_HOSTNAME]);
offer_t offer;
bool gotoffer = false;
while (gotoffer == false) {
printf("sending discover...\n");
if (discover())
return EXIT_FAILURE;
printf("waiting for offer...\n");
while (gotoffer == false) {
if (waitforserver(10000))
break;
printf("got offer!\n");
uint32_t rxid;
if (processoffer(&offer, &rxid))
return EXIT_FAILURE;
printf("processed offer!\n");
// packet wasn't for this machine
if (rxid != xid)
continue;
printf("packet id ok\n");
gotoffer = true;
}
}
bool gotack = false;
while (gotack == false) {
printf("sending request...\n");
if (request(&offer))
return EXIT_FAILURE;
printf("waiting for ack...\n");
while (gotack == false) {
if (waitforserver(10000))
break;
uint32_t rxid;
printf("got ack\n");
if (getack(&rxid))
return EXIT_FAILURE;
printf("ack ok\n");
if (rxid != xid)
continue;
printf("ack id ok\n");
gotack = true;
}
}
printoffer(&offer);
addroute(argv[1], 0, offer.router, 0, 1);
addroute(argv[1], offer.clientip, 0, offer.netmask, 10);
setifip(argv[1], offer.clientip);
writedns(offer.dns);
return EXIT_SUCCESS;
}