1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #define _POSIX_C_SOURCE 200809L
6 
7 #define _GNU_SOURCE
8 #define _DARWIN_C_SOURCE
9 
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <getopt.h>
13 #include <net/if.h>
14 #include <netinet/in.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/time.h>
19 
20 #include "netprotocol.h"
21 
22 static const char* hostname;
23 static struct sockaddr_in6 addr;
24 static bool found = false;
25 static char found_device_nodename[MAX_NODENAME_LENGTH];
26 static bool fuchsia_address = false;
27 static bool local_address = false;
28 static const char* appname;
29 
on_device(device_info_t * device,void * cookie)30 static bool on_device(device_info_t* device, void* cookie) {
31     if (hostname != NULL && strcmp(hostname, device->nodename)) {
32         // Asking for a specific address and this isn't it.
33         return true;
34     }
35 
36     if (found && strcmp(found_device_nodename, device->nodename) != 0) {
37         fprintf(stderr, "Multiple devices found, including %s and %s. Specify a hostname.\n",
38                 found_device_nodename, device->nodename);
39         exit(1);
40     }
41 
42     addr = device->inet6_addr;
43     strncpy(found_device_nodename, device->nodename, MAX_NODENAME_LENGTH);
44     found = true;
45     return true;
46 }
47 
usage(void)48 static void usage(void) {
49     fprintf(stderr, "usage: %s [options] [hostname]\n", appname);
50     netboot_usage(false);
51     fprintf(stderr, "    --fuchsia         Use fuchsia link local addresses.\n");
52     fprintf(stderr, "    --local           Print local address that routes to remote.\n");
53 }
54 
55 static struct option netaddr_opts[] = {
56     {"fuchsia", no_argument, NULL, 'f'},
57     {"local", no_argument, NULL, 'l'},
58     {NULL, 0, NULL, 0},
59 };
60 
netaddr_opt_callback(int ch,int argc,char * const * argv)61 static bool netaddr_opt_callback(int ch, int argc, char* const* argv) {
62     switch (ch) {
63     case 'f':
64         fuchsia_address = true;
65         break;
66     case 'l':
67         local_address = true;
68         break;
69     default:
70         return false;
71     }
72     return true;
73 }
74 
main(int argc,char ** argv)75 int main(int argc, char** argv) {
76     appname = argv[0];
77     int index = netboot_handle_custom_getopt(argc, argv, netaddr_opts, 1, netaddr_opt_callback);
78     if (index < 0) {
79         usage();
80         return -1;
81     }
82 
83     argv += index;
84     argc -= index;
85 
86     if (argc > 1) {
87         usage();
88     }
89 
90     if (argc == 1) {
91         hostname = argv[0];
92         if (!*hostname || (*hostname == ':' && hostname[1] == '\0'))
93             hostname = NULL;
94     }
95 
96     if (netboot_discover(NB_SERVER_PORT, NULL, on_device, NULL) || !found) {
97         fprintf(stderr, "Failed to discover %s\n", hostname ? hostname : "");
98         return 1;
99     }
100 
101     if (local_address) {
102         // Bind an ephemeral UDP socket to the Fuchsia target address, then inspect
103         // the local address it bound to (poor mans portable "lookup route").
104         int s;
105         if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
106             fprintf(stderr, "error: cannot create socket: %s\n", strerror(errno));
107             return -1;
108         }
109         if (connect(s, (const struct sockaddr*)&addr, sizeof(addr)) < 0) {
110             fprintf(stderr, "error: cannot \"connect\" socket: %s\n", strerror(errno));
111             return -1;
112         }
113         socklen_t addrlen = sizeof(addr);
114         if (getsockname(s, (struct sockaddr*)&addr, &addrlen) < 0) {
115             fprintf(stderr, "error: %s\n", strerror(errno));
116             return -1;
117         }
118         shutdown(s, SHUT_RDWR);
119     }
120 
121     if (fuchsia_address) {
122         // Make it a valid link-local address by fiddling some bits.
123         addr.sin6_addr.s6_addr[11] = 0xFF;
124     }
125 
126     // Get the string form of the address.
127     char addr_s[INET6_ADDRSTRLEN];
128     inet_ntop(AF_INET6, &addr.sin6_addr, addr_s, sizeof(addr_s));
129 
130     // Get the name of the interface.
131     char ifname[IF_NAMESIZE];
132     if_indextoname(addr.sin6_scope_id, ifname);
133     fprintf(stdout, "%s%%%s\n", addr_s, ifname);
134 
135     return 0;
136 }
137