netboot.c (6726B)
1 // Copyright 2022, Brian Swetland <swetland@frotz.net> 2 // Licensed under the Apache License, Version 2.0. 3 4 #include <stdio.h> 5 #include <string.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 #include <poll.h> 9 #include <time.h> 10 11 #include <sys/types.h> 12 #include <sys/socket.h> 13 #include <arpa/inet.h> 14 #include <net/if.h> 15 #include <fcntl.h> 16 17 #include "netboot.h" 18 19 uint64_t get_time_ms(void) { 20 struct timespec ts; 21 if (clock_gettime(CLOCK_MONOTONIC, &ts)) { 22 exit(-1); 23 } 24 return (((uint64_t)ts.tv_nsec) / 1000000ULL) + (((uint64_t)ts.tv_sec) * 1000ULL); 25 } 26 27 int open_udp6_socket(struct sockaddr_in6 *addr, int con) { 28 int s, n = 1; 29 if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) { 30 perror("open_udp6_socket: open socket"); 31 return -1; 32 } 33 if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n)) < 0) { 34 perror("open_udp6_socket: SO_REUSEPORT"); 35 goto fail; 36 } 37 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) < 0) { 38 perror("open_udp6_socket: SO_REUSEADDR"); 39 goto fail; 40 } 41 if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) { 42 perror("open_ud6_socket: set O_NONBLOCK"); 43 goto fail; 44 } 45 if (con) { 46 if (connect(s, (struct sockaddr*) addr, sizeof(struct sockaddr_in6)) < 0) { 47 perror("open_udp6_socket: connect"); 48 goto fail; 49 } 50 } else { 51 if (bind(s, (struct sockaddr*) addr, sizeof(struct sockaddr_in6)) < 0) { 52 perror("open_udp6_socket: bind"); 53 goto fail; 54 } 55 } 56 return s; 57 fail: 58 close(s); 59 return -1; 60 } 61 62 static uint32_t seq = 1; 63 64 int open_node(int idx, const char *nodename) { 65 int s, r; 66 67 struct sockaddr_in6 addr = { 68 .sin6_family = AF_INET6, 69 .sin6_scope_id = idx, 70 }; 71 if ((s = open_udp6_socket(&addr, 0)) < 0) { 72 return -1; 73 } 74 75 struct sockaddr_in6 mcast = { 76 .sin6_family = AF_INET6, 77 .sin6_port = htons(NB_PORT_QUERY), 78 .sin6_scope_id = idx, 79 }; 80 inet_pton(AF_INET6, "ff02::1", &mcast.sin6_addr); 81 82 netboot_msg_t msg = { 83 .magic = NB_MAGIC, 84 .cmd = NB_CMD_QUERY, 85 .arg = 0, 86 }; 87 uint32_t msglen = strlen(nodename); 88 if (msglen > NB_MSG_MAX) { 89 return -1; 90 } 91 memcpy(msg.db, nodename, msglen); 92 msglen += NB_MSG_MIN; 93 94 uint64_t next = get_time_ms() + 500; 95 for (;;) { 96 uint64_t now = get_time_ms(); 97 if (now >= next) { 98 next = now + 500; 99 msg.seq = ++seq; 100 r = sendto(s, &msg, msglen, 0, (struct sockaddr*) &mcast, sizeof(mcast)); 101 if (r != msglen) { 102 perror("write"); 103 return -1; 104 } 105 } 106 struct pollfd pfd = { 107 .fd = s, 108 .events = POLLIN, 109 }; 110 if (poll(&pfd, 1, next - now) == 1) { 111 uint8_t rxb[2048]; 112 struct sockaddr_in6 rxaddr; 113 socklen_t rxalen = sizeof(rxaddr); 114 if ((r = recvfrom(s, rxb, sizeof(rxb), 1, (void*)&rxaddr, &rxalen)) > 0) { 115 netboot_msg_t *msg = (void*)rxb; 116 if (r < NB_MSG_MIN) { 117 continue; 118 } 119 if ((msg->magic != NB_MAGIC) || 120 (msg->cmd != NB_CMD_STATUS) || 121 (msg->seq != seq)) { 122 continue; 123 } 124 char _tmp[128]; 125 const char *tmp = inet_ntop(AF_INET6, &rxaddr.sin6_addr, _tmp, sizeof(_tmp)); 126 fprintf(stderr, "\n[ located '%s' at %s/%u ]\n", nodename, 127 tmp ? tmp : "unknown", rxaddr.sin6_scope_id); 128 129 int s0 = open_udp6_socket(&rxaddr, 1); 130 if (s0 >= 0) { 131 close(s); 132 return s0; 133 } 134 } 135 } 136 } 137 } 138 139 140 int send_msg(int s, netboot_msg_t *msg, unsigned len) { 141 int r; 142 for (int retry = 0; retry < 5; retry++) { 143 msg->seq = ++seq; 144 if ((r = write(s, msg, len)) != len) { 145 return -1; 146 } 147 struct pollfd pfd = { 148 .fd = s, 149 .events = POLLIN, 150 }; 151 if (poll(&pfd, 1, 250) != 1) { 152 fprintf(stderr, "T"); 153 continue; 154 } 155 netboot_msg_t rsp; 156 r = read(s, &rsp, sizeof(rsp)); 157 if ((r < NB_MSG_MIN) || (rsp.magic != NB_MAGIC) || (rsp.cmd != NB_CMD_STATUS)) { 158 fprintf(stderr, "X"); 159 continue; 160 } 161 if (rsp.seq != seq) { 162 fprintf(stderr, "S"); 163 continue; 164 } 165 if (rsp.arg != NB_OK) { 166 fprintf(stderr, "E"); 167 return -1; 168 } 169 fprintf(stderr, "."); 170 return 0; 171 } 172 return -1; 173 } 174 175 int send_file(int s, const char* fn, uint32_t addr) { 176 int r; 177 178 netboot_msg_t msg; 179 msg.magic = NB_MAGIC; 180 msg.cmd = NB_CMD_WRITE; 181 182 int fd = open(fn, O_RDONLY); 183 if (fd < 0) { 184 fprintf(stderr, "error: cannot open '%s'\n", fn); 185 return -1; 186 } 187 fprintf(stderr, "\n[ sending '%s' to 0x%08x ]\n", fn, addr); 188 for (;;) { 189 if ((r = read(fd, msg.db, NB_DATA_MAX)) <= 0) { 190 close(fd); 191 return r; 192 } 193 msg.arg = addr; 194 addr += r; 195 if (send_msg(s, &msg, r + NB_MSG_MIN)) { 196 close(fd); 197 return -1; 198 } 199 } 200 } 201 202 typedef struct { 203 const char* name; 204 uint32_t addr; 205 } image_t; 206 207 #define IMGMAX 8 208 209 #define consume() \ 210 do { argc--; argv++; if (argc == 1) { \ 211 fprintf(stderr, "error: missing argument\n"); \ 212 return -1; } \ 213 } while (0) 214 215 void usage(void) { 216 fprintf(stderr, "\n" 217 "usage: netboot <option>* <image>+\n" 218 "\n" 219 "option: -i <interface-name>\n" 220 " -n <node-name>\n" 221 "\n" 222 "image: <filename>@<hex-load-address>\n" 223 "\n" 224 "Send one or more images to <node-name> on <interface-name>.\n" 225 "Start execution at the address of the first image.\n\n"); 226 } 227 228 int main(int argc, char **argv) { 229 int s, idx; 230 231 int do_exit = 0; 232 const char *ifname = "qemu0"; 233 const char *nodename = "device"; 234 image_t imagelist[IMGMAX]; 235 int imagecount = 0; 236 237 char *tmp; 238 while (argc > 1) { 239 if ((tmp = strchr(argv[1], '@'))) { 240 if (imagecount == IMGMAX) { 241 fprintf(stderr, "error: too many images\n"); 242 return -1; 243 } 244 *tmp++ = 0; 245 imagelist[imagecount].name = argv[1]; 246 imagelist[imagecount].addr = strtoul(tmp, NULL, 16); 247 imagecount++; 248 } else if (!strcmp("-i", argv[1])) { 249 consume(); 250 ifname = argv[1]; 251 } else if (!strcmp("-n", argv[1])) { 252 consume(); 253 nodename = argv[1]; 254 } else if (!strcmp("-h", argv[1])) { 255 usage(); 256 return 0; 257 } else if (!strcmp("-x", argv[1])) { 258 do_exit = 1; 259 } else { 260 fprintf(stderr, "error: unknown argument '%s'\n", argv[1]); 261 return -1; 262 } 263 argc--; 264 argv++; 265 } 266 267 if (imagecount == 0) { 268 fprintf(stderr, "error: no files to send?\n"); 269 return -1; 270 } 271 if ((idx = if_nametoindex(ifname)) == 0) { 272 fprintf(stderr, "unknown network interface: '%s'\n", argv[1]); 273 return -1; 274 } 275 276 fprintf(stderr, "\n[ netboot v0.1 ]\n"); 277 fprintf(stderr, "[ netifc '%s' ]\n", ifname); 278 fprintf(stderr, "[ nodename '%s' ]\n", nodename); 279 280 for (;;) { 281 again: 282 if ((s = open_node(idx, "device")) < 0) { 283 return -1; 284 } 285 286 for (unsigned n = 0; n < imagecount; n++) { 287 if (send_file(s, imagelist[n].name, imagelist[n].addr)) { 288 fprintf(stderr, "\n[ send failure ]\n"); 289 close(s); 290 goto again; 291 } 292 } 293 fprintf(stderr, "\n[ start execution at 0x%08x ]\n", imagelist[0].addr); 294 295 netboot_msg_t msg; 296 msg.magic = NB_MAGIC; 297 msg.cmd = NB_CMD_EXEC; 298 msg.seq = ++seq; 299 msg.arg = imagelist[0].addr; 300 send_msg(s, &msg, NB_MSG_MIN); 301 fprintf(stderr, "\n"); 302 close(s); 303 304 if (do_exit) break; 305 } 306 307 return 0; 308 }