blob: 5dd95c16bc56e6885cfbb7da5b74e72dd7cd81ea [file] [log] [blame]
Denys Vlasenkoe39da802017-09-01 12:48:15 +02001/*
2 * Copyright (c) 2017 Denys Vlasenko <vda.linux@googlemail.com>
3 *
4 * Licensed under GPLv2, see file LICENSE in this source tree.
5 */
6//config:config IPCONFIG
7//config: bool "ipconfig"
8//config: default y
9//config: help
10//config: (Auto)configure network.
11
12//applet:IF_IPCONFIG(APPLET(ipconfig, BB_DIR_BIN, BB_SUID_DROP))
13
14//kbuild:lib-$(CONFIG_IPCONFIG) += ipconfig.o
15
16#include <net/if.h>
17#include "libbb.h"
18
19struct globals {
20 int fixed;
21 const char *hostname;
22};
23#define G (*ptr_to_globals)
24#define INIT_G() do { \
25 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
26} while (0)
27
28struct dev {
29 const char *name;
30 uint8_t fixed;
31 uint32_t ip_addr;
32 uint32_t ip_netmask;
33 uint32_t ip_server;
34 uint32_t ip_router;
35};
36
37static int
38parse_method(const char *method)
39{
40 int fixed;
41
42 fixed = (method[0] != '\0');
43 if (fixed) {
44 /* if it's not "" */
45 fixed = index_in_strings(
46 /* 0 */ "on""\0"
47 /* 1 */ "any""\0"
48 /* 2 */ "both""\0"
49 /* 3 */ "dhcp""\0"
50 /* 4 */ "bootp""\0"
51 /* 5 */ "rarp""\0"
52 /* 6 */ "none""\0"
53 /* 7 */ "static""\0"
54 /* 8 */ "off""\0"
55 , method
56 );
57 if (fixed > 0)
58 fixed /= 6;
59 }
60 return fixed;
61}
62
63static uint32_t
64parse_addr(const char *ip)
65{
66 struct in_addr in;
67 if (inet_aton(ip, &in) == 0)
68 bb_error_msg_and_die("bad IP address '%s'", ip);
69 return in.s_addr;
70}
71
72static struct dev*
73find_device(llist_t *iface_list, const char *name)
74{
75 while (iface_list) {
76 struct dev *dev = (void*) iface_list->data;
77 if (strcmp(dev->name, name) == 0)
78 return dev;
79 iface_list = iface_list->link;
80 }
81 return NULL;
82}
83
84static void
85set_from_template(struct dev *dev, struct dev *template)
86{
87 if (template->ip_addr != 0)
88 dev->ip_addr = template->ip_addr;
89 if (template->ip_netmask != 0)
90 dev->ip_netmask = template->ip_netmask;
91 if (template->ip_server != 0)
92 dev->ip_server = template->ip_server;
93 if (template->ip_router != 0)
94 dev->ip_router = template->ip_router;
95 dev->fixed = template->fixed;
96}
97
98// "ip=PROTO" - also implies -o
99// "nfsaddrs=PROTO" - also implies -o
100// "<devname>"
101// "[ip=/nfsaddrs=]IP:SERVER_IP:ROUTER:NETMASK:HOSTNAME:IFACE:METHOD"
102// all optional. trailing empty :: can be skipped, only one : needs to be there
103// (to distinguish from other formats).
104// ":::::eth0" - dhcp on eth0
105// ":" - dhcp on all ifaces
106// "::1.2.3.4" - dhcp on all ifaces, gateway is 1.2.3.4 (fairly nonsensical)
107static void
108add_all_devices(llist_t **iface_list, struct dev *template);
109static struct dev*
110add_device(llist_t **iface_list, char *ip)
111{
112 struct dev *dev;
113
114 dev = xzalloc(sizeof(*dev));
115 dev->fixed = G.fixed;
116
117 if (strncmp("ip=", ip, 3) == 0
118 || strncmp("nfsaddrs=", ip, 9) == 0
119 ) {
120 int fixed;
121
122 ip = strchr(ip, '=') + 1;
123 fixed = parse_method(ip);
124 if (fixed >= 0) {
125 add_all_devices(iface_list, dev);
126 free(dev);
127 return NULL;
128 }
129 }
130
131 if (!strchr(ip, ':')) {
132 dev->name = ip;
133 } else {
134 unsigned opt = 0;
135 while (ip && *ip) {
136 char *next = strchr(ip, ':');
137 if (next)
138 *next++ = '\0';
139 if (opt > 6)
140 bb_error_msg_and_die("too many options for %s", dev->name);
141 if (ip[0]) switch (opt) {
142 case 0:
143 dev->ip_addr = parse_addr(ip);
144 break;
145 case 1:
146 dev->ip_server = parse_addr(ip);
147 break;
148 case 2:
149 dev->ip_router = parse_addr(ip);
150 break;
151 case 3:
152 dev->ip_netmask = parse_addr(ip);
153 break;
154 case 4:
155 if (G.hostname && strcmp(G.hostname, ip) != 0)
156 bb_error_msg_and_die("hostname must be the same");
157 G.hostname = ip;
158 break;
159 case 5:
160 dev->name = ip;
161 break;
162 case 6:
163 dev->fixed = parse_method(ip);
164 break;
165 }
166 ip = next;
167 opt++;
168 }
169 }
170
171 if (dev->name == NULL
172 || strcmp(dev->name, "all") == 0
173 ) {
174 add_all_devices(iface_list, dev);
175 free(dev);
176 return NULL;
177 }
178 llist_add_to_end(iface_list, dev);
179 return dev;
180}
181
182static void
183add_all_devices(llist_t **iface_list, struct dev *template)
184{
185 DIR *d;
186 struct dirent *de;
187#define sys_class_net "/sys/class/net"
188
189 /* All forms of "config all ifaces" imply -o */
190 option_mask32 |= 1;
191
192 d = opendir(sys_class_net);
193 if (!d)
194 return;
195
196 while ((de = readdir(d)) != NULL) {
197 struct dev *dev;
198 char *filename;
199 char p[sizeof(long)*3];
200 unsigned long flags;
201 int r;
202
203 /* Exclude devices beginning with dots as well as . and .. */
204 if (de->d_name[0] == '.')
205 continue;
206 filename = xasprintf("%s/%s/flags", sys_class_net, de->d_name);
207 r = open_read_close(filename, p, sizeof(p) - 1);
208 free(filename);
209 if (r < 0)
210 continue;
211 p[r] = '\0';
212 /* file's format is "0xNNNN\n" */
213 flags = bb_strtoul(p, NULL, 0);
214 /*
215 * Heuristic for if this is a reasonable boot interface.
216 * This is the same logic the in-kernel ipconfig uses.
217 */
218 if (flags & IFF_LOOPBACK)
219 continue;
220 if (!(flags & (IFF_BROADCAST | IFF_POINTOPOINT)))
221 continue;
222 if (find_device(*iface_list, de->d_name))
223 continue;
224 dev = add_device(iface_list, xstrdup(de->d_name));
225 if (dev)
226 set_from_template(dev, template);
227 }
228 closedir(d);
229#undef sys_class_net
230}
231
232//usage:#define ipconfig_trivial_usage
233//usage: "[-c METHOD] [-t TIMEOUT] [-on] [-i VENDOR_ID] [-p PORT] [-d] IFACE..."
234//usage:#define ipconfig_full_usage "\n\n"
235//usage: "(Auto)configure network"
236//usage: "\n"
237//usage: "\n"" -c METHOD off/none/static or on/dhcp (default)"
238//usage: "\n"" -t SECONDS Give up after SECONDS"
239//usage: "\n"" -o Stop after one interface is configured"
240//usage: "\n"" -n Dry run"
241//usage: "\n"" -i VENDOR_ID DHCP vendor id (default '')"
242//usage: "\n"" -p PORT DHCP port to use"
243//usage: "\n"" [-d] IFACE... Interface(s)"
244//usage: "\n"
245//usage: "\n"" IFACE can be:"
246//usage: "\n"" all - configure all interfaces"
247//usage: "\n"" IFACE - configure this interface"
248//usage: "\n"" IP:SERVER_IP:ROUTER:NETMASK:HOSTNAME:IFACE:METHOD (all optional)"
249// TIMEOUT defaults to infinite
250// -d actually is an option with an argument
251// (not a clue why klibc-utils has two ways to specify interfaces)
252int ipconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
253int ipconfig_main(int argc UNUSED_PARAM, char **argv)
254{
255 const char *method = "";
256 const char *vendor_id = "";
257 llist_t *devname_list = NULL;
258 llist_t *iface_list;
259 int timeout = -1;
260 unsigned port;
261 unsigned opt;
262
263 INIT_G();
264
265 opt = getopt32(argv,
266 "onc:t:i:p:+d:*",
267 &method, &timeout, &vendor_id, &port, &devname_list
268 );
269 argv += optind;
270
271 G.fixed = parse_method(method);
272 if (G.fixed < 0)
273 bb_show_usage();
274
275 iface_list = NULL;
276 while (devname_list)
277 add_device(&iface_list, (char*) llist_pop(&devname_list));
278 while (*argv)
279 add_device(&iface_list, *argv++);
280
281 while (iface_list) {
282 struct dev *dev = (void*) iface_list->data;
283 printf("name:'%s'\n", dev->name);
284 printf("fixed:%u\n" , dev->fixed);
285 printf("ip:%s/" , inet_ntoa(*(struct in_addr*)&dev->ip_addr));
286 printf("%s\n" , inet_ntoa(*(struct in_addr*)&dev->ip_netmask));
287 printf("server:%s\n", inet_ntoa(*(struct in_addr*)&dev->ip_server));
288 printf("router:%s\n", inet_ntoa(*(struct in_addr*)&dev->ip_router));
289 iface_list = iface_list->link;
290 }
291 bb_error_msg("hostname:'%s'", G.hostname);
292 bb_error_msg("fixed:%u", G.fixed);
293
294 return EXIT_SUCCESS;
295}
296//After device is configured, write out a "/run/net-IFACE.conf" file:
297// // udchcp env values:
298//write_option("DEVICE", dev->name); interface=eth0
299//write_option("PROTO", method);
300//write_option("IPV4ADDR", dev->ip_addr); ip=10.43.17.38
301//write_option("IPV4BROADCAST", dev->ip_broadcast); subnet=255.255.255.0 mask=24
302//write_option("IPV4NETMASK", dev->ip_netmask); subnet=255.255.255.0 mask=24
303//write_option("IPV4GATEWAY", dev->ip_gateway); router=10.43.17.254
304//write_option("IPV4DNS0", dev->ip_nameserver[0]); dns=10.38.5.26 10.11.5.19
305//write_option("IPV4DNS1", dev->ip_nameserver[1]); dns=10.38.5.26 10.11.5.19
306//write_option("HOSTNAME", dev->hostname); hostname="STR"
307//write_option("DNSDOMAIN", dev->dnsdomainname); domain=domain.com
308//write_option("NISDOMAIN", dev->nisdomainname); nisdomain="STR"
309//write_option("ROOTSERVER", my_inet_ntoa(dev->ip_server)); serverid=10.44.6.2
310//write_option("ROOTPATH", dev->bootpath); rootpath="STR"
311//write_option("filename", dev->filename); boot_file=/pxelinux.0
312//write_option("UPTIME", dev->uptime); sysinfo()->uptime
313//write_option("DHCPLEASETIME", dev->dhcpleasetime); lease=44148
314//write_option("DOMAINSEARCH", dev->domainsearch); search="ABC DEF"
315//
316//(write_option writes out single-quote escaped string, VAR='VAL')