| // Copyright 2017 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #define _POSIX_C_SOURCE 200809L | |
| #define _GNU_SOURCE | |
| #define _DARWIN_C_SOURCE | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <getopt.h> | |
| #include <net/if.h> | |
| #include <netinet/in.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/time.h> | |
| #include "netprotocol.h" | |
| static const char* hostname; | |
| static struct sockaddr_in6 addr; | |
| static bool found = false; | |
| static char found_device_nodename[MAX_NODENAME_LENGTH]; | |
| static bool fuchsia_address = false; | |
| static bool local_address = false; | |
| static const char* appname; | |
| static bool on_device(device_info_t* device, void* cookie) { | |
| if (hostname != NULL && strcmp(hostname, device->nodename)) { | |
| // Asking for a specific address and this isn't it. | |
| return true; | |
| } | |
| if (found && strcmp(found_device_nodename, device->nodename) != 0) { | |
| fprintf(stderr, "Multiple devices found, including %s and %s. Specify a hostname.\n", | |
| found_device_nodename, device->nodename); | |
| exit(1); | |
| } | |
| addr = device->inet6_addr; | |
| strncpy(found_device_nodename, device->nodename, MAX_NODENAME_LENGTH); | |
| found = true; | |
| return true; | |
| } | |
| static void usage(void) { | |
| fprintf(stderr, "usage: %s [options] [hostname]\n", appname); | |
| netboot_usage(false); | |
| fprintf(stderr, " --fuchsia Use fuchsia link local addresses.\n"); | |
| fprintf(stderr, " --local Print local address that routes to remote.\n"); | |
| } | |
| static struct option netaddr_opts[] = { | |
| {"fuchsia", no_argument, NULL, 'f'}, | |
| {"local", no_argument, NULL, 'l'}, | |
| {NULL, 0, NULL, 0}, | |
| }; | |
| static bool netaddr_opt_callback(int ch, int argc, char* const* argv) { | |
| switch (ch) { | |
| case 'f': | |
| fuchsia_address = true; | |
| break; | |
| case 'l': | |
| local_address = true; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| int main(int argc, char** argv) { | |
| appname = argv[0]; | |
| int index = netboot_handle_custom_getopt(argc, argv, netaddr_opts, netaddr_opt_callback); | |
| if (index < 0) { | |
| usage(); | |
| return -1; | |
| } | |
| argv += index; | |
| argc -= index; | |
| if (argc > 1) { | |
| usage(); | |
| } | |
| if (argc == 1) { | |
| hostname = argv[0]; | |
| if (!*hostname || (*hostname == ':' && hostname[1] == '\0')) | |
| hostname = NULL; | |
| } | |
| if (netboot_discover(NB_SERVER_PORT, NULL, on_device, NULL) || !found) { | |
| fprintf(stderr, "Failed to discover %s\n", hostname ? hostname : ""); | |
| return 1; | |
| } | |
| if (local_address) { | |
| // Bind an ephemeral UDP socket to the Fuchsia target address, then inspect | |
| // the local address it bound to (poor mans portable "lookup route"). | |
| int s; | |
| if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | |
| fprintf(stderr, "error: cannot create socket: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| if (connect(s, (const struct sockaddr*)&addr, sizeof(addr)) < 0) { | |
| fprintf(stderr, "error: cannot \"connect\" socket: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| socklen_t addrlen = sizeof(addr); | |
| if (getsockname(s, (struct sockaddr*)&addr, &addrlen) < 0) { | |
| fprintf(stderr, "error: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| shutdown(s, SHUT_RDWR); | |
| } | |
| if (fuchsia_address) { | |
| // Make it a valid link-local address by fiddling some bits. | |
| addr.sin6_addr.s6_addr[11] = 0xFF; | |
| } | |
| // Get the string form of the address. | |
| char tmp[INET6_ADDRSTRLEN]; | |
| const char* addr_s = inet_ntop(AF_INET6, &addr.sin6_addr, tmp, sizeof(tmp)); | |
| if (addr_s == NULL) { | |
| fprintf(stderr, "error: %s\n", strerror(errno)); | |
| return -1; | |
| } | |
| fprintf(stdout, "%s%%%d\n", addr_s, addr.sin6_scope_id); | |
| return 0; | |
| } |