commit c484f4fe3bd08fe03a55ae70f7091020018d6def
parent 600bb479fe6e810286dfc133040072265f03d97a
Author: Brian Swetland <swetland@frotz.net>
Date: Mon, 23 May 2022 01:38:45 -0700
netboot: add ipv6 netboot utility
Diffstat:
3 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -44,7 +44,7 @@ BUILD := out
CFLAGS := -g -Wall -Ihw/inc
CFLAGS += -O2
-ALL :=
+ALL := out/netboot
LIBC_SRC := libc/src/printf.c $(wildcard libc/src/string/*.c)
@@ -55,3 +55,7 @@ build-all: $(ALL)
clean::
rm -rf $(BUILD)
+
+out/netboot: tools/netboot.c tools/netboot.h
+ $(CC) -Wall -O2 -g -o out/netboot tools/netboot.c
+
diff --git a/tools/netboot.c b/tools/netboot.c
@@ -0,0 +1,297 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <poll.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <fcntl.h>
+
+#include "netboot.h"
+
+uint64_t get_time_ms(void) {
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
+ exit(-1);
+ }
+ return (((uint64_t)ts.tv_nsec) / 1000000ULL) + (((uint64_t)ts.tv_sec) * 1000ULL);
+}
+
+int open_udp6_socket(struct sockaddr_in6 *addr, int con) {
+ int s, n = 1;
+ if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+ perror("open_udp6_socket: open socket");
+ return -1;
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n)) < 0) {
+ perror("open_udp6_socket: SO_REUSEPORT");
+ goto fail;
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) < 0) {
+ perror("open_udp6_socket: SO_REUSEADDR");
+ goto fail;
+ }
+ if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
+ perror("open_ud6_socket: set O_NONBLOCK");
+ goto fail;
+ }
+ if (con) {
+ if (connect(s, (struct sockaddr*) addr, sizeof(struct sockaddr_in6)) < 0) {
+ perror("open_udp6_socket: connect");
+ goto fail;
+ }
+ } else {
+ if (bind(s, (struct sockaddr*) addr, sizeof(struct sockaddr_in6)) < 0) {
+ perror("open_udp6_socket: bind");
+ goto fail;
+ }
+ }
+ return s;
+fail:
+ close(s);
+ return -1;
+}
+
+static uint32_t seq = 1;
+
+int open_node(int idx, const char *nodename) {
+ int s, r;
+
+ struct sockaddr_in6 addr = {
+ .sin6_family = AF_INET6,
+ .sin6_scope_id = idx,
+ };
+ if ((s = open_udp6_socket(&addr, 0)) < 0) {
+ return -1;
+ }
+
+ struct sockaddr_in6 mcast = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(NB_PORT_QUERY),
+ .sin6_scope_id = idx,
+ };
+ inet_pton(AF_INET6, "ff02::1", &mcast.sin6_addr);
+
+ netboot_msg_t msg = {
+ .magic = NB_MAGIC,
+ .cmd = NB_CMD_QUERY,
+ .arg = 0,
+ };
+ uint32_t msglen = strlen(nodename);
+ if (msglen > NB_MSG_MAX) {
+ return -1;
+ }
+ memcpy(msg.db, nodename, msglen);
+ msglen += NB_MSG_MIN;
+
+ uint64_t next = get_time_ms() + 500;
+ for (;;) {
+ uint64_t now = get_time_ms();
+ if (now >= next) {
+ next = now + 500;
+ msg.seq = ++seq;
+ r = sendto(s, &msg, msglen, 0, (struct sockaddr*) &mcast, sizeof(mcast));
+ if (r != msglen) {
+ perror("write");
+ return -1;
+ }
+ }
+ struct pollfd pfd = {
+ .fd = s,
+ .events = POLLIN,
+ };
+ if (poll(&pfd, 1, next - now) == 1) {
+ uint8_t rxb[2048];
+ struct sockaddr_in6 rxaddr;
+ socklen_t rxalen = sizeof(rxaddr);
+ if ((r = recvfrom(s, rxb, sizeof(rxb), 1, (void*)&rxaddr, &rxalen)) > 0) {
+ netboot_msg_t *msg = (void*)rxb;
+ if (r < NB_MSG_MIN) {
+ continue;
+ }
+ if ((msg->magic != NB_MAGIC) ||
+ (msg->cmd != NB_CMD_STATUS) ||
+ (msg->seq != seq)) {
+ continue;
+ }
+ char _tmp[128];
+ const char *tmp = inet_ntop(AF_INET6, &rxaddr.sin6_addr, _tmp, sizeof(_tmp));
+ fprintf(stderr, "[ located '%s' at %s/%u ]\n", nodename,
+ tmp ? tmp : "unknown", rxaddr.sin6_scope_id);
+
+ int s0 = open_udp6_socket(&rxaddr, 1);
+ if (s0 >= 0) {
+ close(s);
+ return s0;
+ }
+ }
+ }
+ }
+}
+
+
+int send_msg(int s, netboot_msg_t *msg, unsigned len) {
+ int r;
+ for (int retry = 0; retry < 5; retry++) {
+ if ((r = write(s, msg, len)) != len) {
+ return -1;
+ }
+ struct pollfd pfd = {
+ .fd = s,
+ .events = POLLIN,
+ };
+ if (poll(&pfd, 1, 250) != 1) {
+ fprintf(stderr, "T");
+ continue;
+ }
+ netboot_msg_t rsp;
+ r = read(s, &rsp, sizeof(rsp));
+ if ((r < NB_MSG_MIN) || (rsp.magic != NB_MAGIC) || (rsp.cmd != NB_CMD_STATUS)) {
+ fprintf(stderr, "X");
+ continue;
+ }
+ if (rsp.arg != NB_OK) {
+ fprintf(stderr, "E");
+ return -1;
+ }
+ fprintf(stderr, ".");
+ return 0;
+ }
+ return -1;
+}
+
+int send_file(int s, const char* fn, uint32_t addr) {
+ int r;
+
+ netboot_msg_t msg;
+ msg.magic = NB_MAGIC;
+ msg.cmd = NB_CMD_WRITE;
+
+ int fd = open(fn, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "error: cannot open '%s'\n", fn);
+ return -1;
+ }
+ fprintf(stderr, "[ sending '%s' to 0x%08x ]\n", fn, addr);
+ for (;;) {
+ if ((r = read(fd, msg.db, NB_DATA_MAX)) <= 0) {
+ close(fd);
+ return r;
+ }
+ msg.arg = addr;
+ addr += r;
+ if (send_msg(s, &msg, r + NB_MSG_MIN)) {
+ close(fd);
+ return -1;
+ }
+ }
+}
+
+typedef struct {
+ const char* name;
+ uint32_t addr;
+} image_t;
+
+#define IMGMAX 8
+
+#define consume() \
+ do { argc--; argv++; if (argc == 1) { \
+ fprintf(stderr, "error: missing argument\n"); \
+ return -1; } \
+ } while (0)
+
+void usage(void) {
+ fprintf(stderr, "\n"
+ "usage: netboot <option>* <image>+\n"
+ "\n"
+ "option: -i <interface-name>\n"
+ " -n <node-name>\n"
+ "\n"
+ "image: <filename>@<hex-load-address>\n"
+ "\n"
+ "Send one or more images to <node-name> on <interface-name>.\n"
+ "Start execution at the address of the first image.\n\n");
+}
+
+int main(int argc, char **argv) {
+ int s, idx;
+
+ const char *ifname = "qemu0";
+ const char *nodename = "device";
+ image_t imagelist[IMGMAX];
+ int imagecount = 0;
+
+ char *tmp;
+ while (argc > 1) {
+ if ((tmp = strchr(argv[1], '@'))) {
+ if (imagecount == IMGMAX) {
+ fprintf(stderr, "error: too many images\n");
+ return -1;
+ }
+ *tmp++ = 0;
+ imagelist[imagecount].name = argv[1];
+ imagelist[imagecount].addr = strtoul(tmp, NULL, 16);
+ imagecount++;
+ } else if (!strcmp("-i", argv[1])) {
+ consume();
+ ifname = argv[1];
+ } else if (!strcmp("-n", argv[1])) {
+ consume();
+ nodename = argv[1];
+ } else if (!strcmp("-h", argv[1])) {
+ usage();
+ return 0;
+ } else {
+ fprintf(stderr, "error: unknown argument '%s'\n", argv[1]);
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+
+ if (imagecount == 0) {
+ fprintf(stderr, "error: no files to send?\n");
+ return -1;
+ }
+ if ((idx = if_nametoindex(ifname)) == 0) {
+ fprintf(stderr, "unknown network interface: '%s'\n", argv[1]);
+ return -1;
+ }
+
+ fprintf(stderr, "\n[ netboot v0.1 ]\n");
+ fprintf(stderr, "[ netifc '%s' ]\n", ifname);
+ fprintf(stderr, "[ nodename '%s' ]\n", nodename);
+
+ for (;;) {
+again:
+ if ((s = open_node(idx, "device")) < 0) {
+ return -1;
+ }
+
+ for (unsigned n = 0; n < imagecount; n++) {
+ if (send_file(s, imagelist[n].name, imagelist[n].addr)) {
+ fprintf(stderr, "\n[ send failure ]\n");
+ close(s);
+ goto again;
+ }
+ }
+ fprintf(stderr, "\n[ start execution at 0x%08x ]\n", imagelist[0].addr);
+
+ netboot_msg_t msg;
+ msg.magic = NB_MAGIC;
+ msg.cmd = NB_CMD_EXEC;
+ msg.seq = ++seq;
+ msg.arg = imagelist[0].addr;
+ send_msg(s, &msg, NB_MSG_MIN);
+ fprintf(stderr, "\n");
+ }
+
+ return 0;
+}
diff --git a/tools/netboot.h b/tools/netboot.h
@@ -0,0 +1,45 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#pragma once
+
+#include <stdint.h>
+
+#define NB_PORT_QUERY 32320
+#define NB_PORT_CTRL 31310
+
+// echo -n netboot.magic | sha256sum | cut -c1-8
+// commands are similar (netboot.cmd.name)
+#define NB_MAGIC 0x1e5dae11U
+
+// host to target commands
+#define NB_CMD_QUERY 0x56a938e6U // arg=0, db=targetname.ascii
+#define NB_CMD_WRITE 0x7b639621U // arg=addr, db=bytes
+#define NB_CMD_READ 0x1c1d1ecfU // arg=addr, reply.db=bytes
+#define NB_CMD_EXEC 0xbdb9a844U // arg=addr
+
+// target to host responses
+#define NB_CMD_STATUS 0x9e68e03bU // arg=status (0=OK)
+
+// target to multicast messages
+#define NB_CMD_SYSLOG 0x5a374503U // arg=0, db=logmsgs.ascii
+
+#define NB_OK 0
+#define NB_ERR_BADCMD 1
+#define NB_ERR_PARAM 2
+
+#define NB_MSG_MIN 16
+#define NB_MSG_MAX (16 + 1024)
+#define NB_DATA_MAX 1024
+
+typedef struct {
+ uint32_t magic;
+ uint32_t cmd;
+ uint32_t seq;
+ uint32_t arg;
+ union {
+ uint8_t db[1024];
+ uint32_t dw[1024 / 4];
+ };
+} netboot_msg_t;
+