ready for tagging
[util-vserver.git] / src / naddress.c
1 // $Id: naddress.c 2584 2007-08-10 15:28:42Z dhozac $
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // Copyright (C) 2006 Daniel Hokka Zakrisson <daniel@hozac.com>
5 // based on chbind.cc by Jacques Gelinas
6 //  
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2, or (at your option)
10 // any later version.
11 //  
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //  
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include "vserver.h"
26 #include "util.h"
27
28 #include <lib/internal.h>
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <netdb.h>
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <net/if.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <getopt.h>
40 #include <fcntl.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43
44 #define ENSC_WRAPPERS_PREFIX    "naddress: "
45 #define ENSC_WRAPPERS_IO        1
46 #define ENSC_WRAPPERS_UNISTD    1
47 #define ENSC_WRAPPERS_VSERVER   1
48 #include "wrappers.h"
49
50 #define CMD_HELP        0x1000
51 #define CMD_VERSION     0x1001
52
53 #define CMD_SILENT      0x2000
54 #define CMD_NID         0x2001
55 #define CMD_ADD         0x2002
56 #define CMD_REMOVE      0x2003
57 #define CMD_SET         0x2004
58 #define CMD_IP          0x2010
59 #define CMD_BCAST       0x2011
60 #define CMD_MASK        0x2012
61 #define CMD_RANGE       0x2013
62 #define CMD_LBACK       0x2014
63
64 int wrapper_exit_code = 255;
65
66
67 static struct option const
68 CMDLINE_OPTIONS[] = {
69   { "help",     no_argument,  0, CMD_HELP },
70   { "version",  no_argument,  0, CMD_VERSION },
71   { "silent",   no_argument,  0, CMD_SILENT },
72   { "add",      no_argument,  0, CMD_ADD },
73   { "remove",   no_argument,  0, CMD_REMOVE },
74   { "set",      no_argument,  0, CMD_SET },
75   { "nid",      required_argument, 0, CMD_NID },
76   { "ip",       required_argument, 0, CMD_IP },
77   { "mask",     required_argument, 0, CMD_MASK },
78   { "range",    required_argument, 0, CMD_RANGE },
79   { "bcast",    required_argument, 0, CMD_BCAST },
80   { "lback",    required_argument, 0, CMD_LBACK },
81   { 0,0,0,0 }
82 };
83
84 struct vc_ips {
85   struct vc_net_addr a;
86   struct vc_ips *next;
87 };
88
89 struct Arguments {
90   nid_t         nid;
91   struct vc_ips head;
92   bool          is_silent;
93   bool          do_add;
94   bool          do_remove;
95   bool          do_set;
96 };
97
98 static void
99 showHelp(int fd, char const *cmd, int res)
100 {
101   WRITE_MSG(fd, "Usage:\n  ");
102   WRITE_STR(fd, cmd);
103   WRITE_MSG(fd,
104             " (--add|--remove|--set) [--silent] [--nid <nid>]\n"
105             "    [--ip <ip_num>[/<mask>]] [--bcast <broadcast>] [--] <commands> <args>*\n\n"
106             "Please report bugs to " PACKAGE_BUGREPORT "\n");
107
108   exit(res);
109 }
110
111 static void
112 showVersion()
113 {
114   WRITE_MSG(1,
115             "naddress " VERSION " -- bind to an ip and execute a program\n"
116             "This program is part of " PACKAGE_STRING "\n\n"
117             "Copyright (C) 2003,2004 Enrico Scholz\n"
118             "Copyright (C) 2006 Daniel Hokka Zakrisson\n"
119             VERSION_COPYRIGHT_DISCLAIMER);
120   exit(0);
121 }
122
123 /*
124         Check if a network device exist in /proc/net/dev.
125         This is used because ifconfig_ioctl triggers modprobe if requesting
126         information about non existant devices.
127
128         Return != 0 if the device exist.
129 */
130 static bool
131 existsDevice(char const *dev_raw)
132 {
133   size_t        buf_size=8192;
134   char          dev[strlen(dev_raw)+2];
135
136   strcpy(dev, dev_raw);
137   strcat(dev, ":");
138   for (;;) {
139     char        buf[buf_size];
140     char *      pos;
141     bool        too_small;
142     int         fd=open("/proc/net/dev", O_RDONLY);
143     
144     if (fd==-1) return false;
145     too_small = EreadAll(fd, buf, buf_size);
146     close(fd);
147
148     if (too_small) {
149       buf_size *= 2;
150       continue;
151     }
152
153     pos = strstr(buf, dev);
154     return (pos && (pos==buf || pos[-1]==' ' || pos[-1]=='\n'));
155   }
156 }
157
158 static int ifconfig_ioctl(
159         int fd,
160         const char *ifname,
161         int cmd,
162         struct ifreq *ifr)
163 {
164         strcpy(ifr->ifr_name, ifname);
165         return ioctl(fd, cmd, ifr);
166 }
167
168 /*
169         Fetch the IP number of an interface from the kernel.
170         Assume the device is already available in the kernel
171         Return -1 if any error.
172 */
173 int ifconfig_getaddr (
174         const char *ifname,
175         uint32_t *addr,
176         uint32_t *mask,
177         uint32_t *bcast)
178 {
179         int ret = -1;
180         if (existsDevice(ifname)){
181                 int skfd = socket(AF_INET, SOCK_DGRAM, 0);
182                 if (skfd != -1){
183                         struct ifreq ifr;
184                         if (addr != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFADDR, &ifr) >= 0){
185                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
186                                 *addr = sin->sin_addr.s_addr;
187                                 ret = 0;
188                         }
189                         if (mask != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFNETMASK, &ifr) >= 0){
190                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
191                                 *mask = sin->sin_addr.s_addr;
192                                 ret = 0;
193                         }
194                         if (bcast != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFBRDADDR, &ifr) >= 0){
195                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
196                                 *bcast = sin->sin_addr.s_addr;
197                                 ret = 0;
198                         }
199                         close (skfd);
200                 }
201         }
202         return ret;
203 }
204
205 static int
206 convertAddress(const char *str, uint16_t *type, void *dst)
207 {
208   int   ret;
209   if (type) *type = VC_NXA_TYPE_IPV4;
210   ret = inet_pton(AF_INET, str, dst);
211   if (ret==0) {
212     if (type) *type = VC_NXA_TYPE_IPV6;
213     ret = inet_pton(AF_INET6, str, dst);
214   }
215   return ret > 0 ? 0 : -1;
216 }
217
218 static void
219 ipv6PrefixToMask(struct in6_addr *mask, int prefix)
220 {
221   int i;
222   mask->s6_addr32[0] = mask->s6_addr32[1] = mask->s6_addr32[2] = mask->s6_addr32[3] = 0;
223   for (i = 0; (i << 3) < prefix; i++) {
224     mask->s6_addr[i] = 0xff;
225   }
226   if ((i << 3) > prefix)
227     mask->s6_addr[i-1] = ~((1 << (prefix & 0x07)) - 1);
228 }
229
230 static int
231 maskToPrefix(void *data, int limit)
232 {
233   uint8_t *mask = data;
234   int prefix;
235   for (prefix = 0; prefix < limit && mask[prefix >> 3] & (1 << (prefix & 0x07)); prefix++)
236     ;
237   return prefix;
238 }
239
240 static void
241 readIP(char const *str, struct vc_ips **ips, uint16_t type)
242 {
243   if (ifconfig_getaddr(str, &(*ips)->a.vna_v4_ip.s_addr, &(*ips)->a.vna_v4_mask.s_addr, NULL)==-1) {
244     char                *pt;
245     char                tmpopt[strlen(str)+1];
246
247     strcpy(tmpopt,str);
248     pt = strchr(tmpopt,'/');
249     if (pt)
250       *pt++ = '\0';
251
252     if (convertAddress(tmpopt, &(*ips)->a.vna_type, &(*ips)->a.vna_v4_ip.s_addr) == -1) {
253       WRITE_MSG(2, "Invalid IP number '");
254       WRITE_STR(2, tmpopt);
255       WRITE_MSG(2, "'\n");
256       exit(wrapper_exit_code);
257     }
258
259     if (pt==0) {
260       switch ((*ips)->a.vna_type) {
261         case VC_NXA_TYPE_IPV4:
262           (*ips)->a.vna_v4_mask.s_addr = htonl(0xffffff00);
263           (*ips)->a.vna_prefix = 24;
264           break;
265         case VC_NXA_TYPE_IPV6:
266           (*ips)->a.vna_prefix = 64;
267           (*ips)->a.vna_v6_mask.s6_addr32[0] = (*ips)->a.vna_v6_mask.s6_addr32[1] = 0xffffffff;
268           (*ips)->a.vna_v6_mask.s6_addr32[2] = (*ips)->a.vna_v6_mask.s6_addr32[3] = 0x00000000;
269           break;
270         default: break;
271       }
272     }
273     else {
274       // Ok, we have a network size, not a netmask
275       if (strchr(pt,'.')==0 && strchr(pt,':')==0) {
276         unsigned long   sz, limit = 0;
277
278         switch ((*ips)->a.vna_type) {
279           case VC_NXA_TYPE_IPV4: limit =  32;   break;
280           case VC_NXA_TYPE_IPV6: limit = 128;   break;
281           default:                              break;
282         }
283
284         if (!isNumberUnsigned(pt, &sz, true) || sz > limit) {
285           WRITE_MSG(2, "Invalid prefix '");
286           WRITE_STR(2, pt);
287           WRITE_MSG(2, "'\n");
288           exit(wrapper_exit_code);
289         }
290
291         (*ips)->a.vna_prefix = sz;
292         switch ((*ips)->a.vna_type) {
293           case VC_NXA_TYPE_IPV4:
294             (*ips)->a.vna_v4_mask.s_addr = htonl(~((1 << (32 - sz)) - 1));
295             break;
296           case VC_NXA_TYPE_IPV6:
297             ipv6PrefixToMask(&(*ips)->a.vna_v6_mask, (*ips)->a.vna_prefix);
298             break;
299           default: break;
300         }
301       }
302       else {
303         int af, limit;
304         void *mask;
305         switch ((*ips)->a.vna_type) {
306           case VC_NXA_TYPE_IPV4:
307             af = AF_INET;
308             mask = &(*ips)->a.vna_v4_mask.s_addr;
309             limit = 32;
310             break;
311           case VC_NXA_TYPE_IPV6:
312             af = AF_INET6;
313             mask = (*ips)->a.vna_v6_mask.s6_addr32;
314             limit = 128;
315             break;
316           default:
317             return;
318         }
319         if (inet_pton(af, pt, mask) < 0) {
320           WRITE_MSG(2, "Invalid netmask '");
321           WRITE_STR(2, pt);
322           WRITE_MSG(2, "'\n");
323           exit(wrapper_exit_code);
324         }
325         (*ips)->a.vna_prefix = maskToPrefix(mask, limit);
326       }
327     }
328   }
329   else
330     (*ips)->a.vna_type = VC_NXA_TYPE_IPV4;
331   (*ips)->a.vna_type |= type;
332
333   (*ips)->next = calloc(1, sizeof(struct vc_ips));
334   *ips = (*ips)->next;
335 }
336
337 static void
338 readBcast(char const *str, struct vc_ips **ips)
339 {
340   uint32_t bcast;
341   if (ifconfig_getaddr(str, NULL, NULL, &bcast)==-1){
342     if (inet_pton(AF_INET, str, &bcast) < 0) {
343       WRITE_MSG(2, "Invalid broadcast number '");
344       WRITE_STR(2, optarg);
345       WRITE_MSG(2, "'\n");
346       exit(wrapper_exit_code);
347     }
348   }
349   (*ips)->a.vna_v4_ip.s_addr = bcast;
350   (*ips)->a.vna_type = VC_NXA_TYPE_IPV4 | VC_NXA_MOD_BCAST | VC_NXA_TYPE_ADDR;
351   (*ips)->next = calloc(1, sizeof(struct vc_ips));
352   *ips = (*ips)->next;
353 }
354
355 static void
356 tellAddress(struct vc_net_addr *addr, bool silent)
357 {
358   char buf[41];
359   int af;
360   void *address;
361   if (silent)
362     return;
363   switch (addr->vna_type & (VC_NXA_TYPE_IPV4 | VC_NXA_TYPE_IPV6)) {
364     case VC_NXA_TYPE_IPV4:
365       af = AF_INET;
366       address = &addr->vna_v4_ip.s_addr;
367       break;
368     case VC_NXA_TYPE_IPV6:
369       af = AF_INET6;
370       address = addr->vna_v6_ip.s6_addr32;
371       break;
372     default:
373       WRITE_MSG(1, " <unknown address type>");
374       return;
375   }
376   if (inet_ntop(af, address, buf, sizeof(buf)) == NULL) {
377     WRITE_MSG(1, " <conversion failed>");
378     return;
379   }
380   WRITE_MSG(1, " ");
381   WRITE_STR(1, buf);
382 }
383
384 static inline void
385 doit(struct Arguments *args)
386 {
387   struct vc_ips *ips;
388
389   if (args->do_set) {
390     struct vc_net_addr remove = { .vna_type = VC_NXA_TYPE_ANY };
391     if (vc_net_remove(args->nid, &remove) == -1) {
392       perror(ENSC_WRAPPERS_PREFIX "vc_net_remove()");
393       exit(wrapper_exit_code);
394     }
395   }
396
397   if (args->do_add || args->do_set) {
398     if (!args->is_silent)
399       WRITE_MSG(1, "Adding");
400     for (ips = &args->head; ips->next; ips = ips->next) {
401       tellAddress(&ips->a, args->is_silent);
402       if (vc_net_add(args->nid, &ips->a) == -1) {
403         if (!args->is_silent)
404           WRITE_MSG(1, "\n");
405         if (!args->is_silent)
406           WRITE_MSG(1, "\n");
407         perror(ENSC_WRAPPERS_PREFIX "vc_net_add()");
408         exit(wrapper_exit_code);
409       }
410     }
411     if (!args->is_silent)
412       WRITE_MSG(1, "\n");
413   }
414   else if (args->do_remove) {
415     if (!args->is_silent)
416       WRITE_MSG(1, "Removing");
417     for (ips = &args->head; ips->next; ips = ips->next) {
418       tellAddress(&ips->a, args->is_silent);
419       if (vc_net_remove(args->nid, &ips->a) == -1) {
420         if (!args->is_silent)
421           WRITE_MSG(1, "\n");
422         perror(ENSC_WRAPPERS_PREFIX "vc_net_remove()");
423         exit(wrapper_exit_code);
424       }
425     }
426     if (!args->is_silent)
427       WRITE_MSG(1, "\n");
428   }
429 }
430
431 int main (int argc, char *argv[])
432 {
433   struct Arguments args = {
434     .nid        = VC_NOCTX,
435     .is_silent  = false,
436     .do_add     = false,
437     .do_remove  = false,
438     .do_set     = false,
439     .head       = { .next = NULL },
440   };
441   struct vc_ips *ips = &args.head;
442   
443   while (1) {
444     int         c = getopt_long(argc, argv, "+", CMDLINE_OPTIONS, 0);
445     if (c==-1) break;
446
447     switch (c) {
448       case CMD_HELP     :  showHelp(1, argv[0], 0);
449       case CMD_VERSION  :  showVersion();
450       case CMD_SILENT   :  args.is_silent = true; break;
451       case CMD_NID      :  args.nid       = Evc_nidopt2nid(optarg,true); break;
452       case CMD_ADD      :  args.do_add    = true; break;
453       case CMD_REMOVE   :  args.do_remove = true; break;
454       case CMD_SET      :  args.do_set    = true; break;
455       case CMD_IP       :  readIP(optarg, &ips, VC_NXA_TYPE_ADDR);  break;
456       case CMD_MASK     :  readIP(optarg, &ips, VC_NXA_TYPE_MASK);  break;
457       case CMD_RANGE    :  readIP(optarg, &ips, VC_NXA_TYPE_RANGE); break;
458       case CMD_BCAST    :  readBcast(optarg, &ips); break;
459       case CMD_LBACK    :  readIP(optarg, &ips, VC_NXA_TYPE_ADDR | VC_NXA_MOD_LBACK); break;
460       default           :
461         WRITE_MSG(2, "Try '");
462         WRITE_STR(2, argv[0]);
463         WRITE_MSG(2, " --help' for more information.\n");
464         exit(wrapper_exit_code);
465         break;
466     }
467   }
468
469   if (args.nid == VC_NOCTX) args.nid = Evc_get_task_nid(0);
470
471   if (!args.do_add && !args.do_remove && !args.do_set) {
472     WRITE_MSG(2, "No operation specified; try '--help' for more information\n");
473     exit(wrapper_exit_code);
474   }
475   else if (((args.do_add ? 1 : 0) + (args.do_remove ? 1 : 0) + (args.do_set ? 1 : 0)) > 1) {
476     WRITE_MSG(2, "Multiple operations specified; try '--help' for more information\n");
477     exit(wrapper_exit_code);
478   }
479
480   doit(&args);
481
482   if (optind != argc)
483     Eexecvp (argv[optind],argv+optind);
484   return EXIT_SUCCESS;
485 }
486
487 #ifdef ENSC_TESTSUITE
488 #include <assert.h>
489
490 void test()
491 {
492   struct vc_ip_mask_pair        ip;
493   uint32_t                      bcast;
494
495   bcast = 0;
496   readIP("1.2.3.4", &ip, &bcast);
497   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffffff00) && bcast==0);
498
499   readIP("1.2.3.4/8", &ip, &bcast);
500   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xff000000) && bcast==0);
501
502   readIP("1.2.3.4/255.255.0.0", &ip, &bcast);
503   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffff0000) && bcast==0);
504
505   readIP("localhost", &ip, &bcast);
506   assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xffffff00) && bcast==0);
507
508 #if 0
509   if (ifconfig_getaddr("lo", &tmp, &tmp, &tmp)!=-1) {
510     readIP("lo", &ip, &bcast);
511     assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xff000000) && bcast==ntohl(0x7fffffff));
512   }
513 #endif
514 }
515 #endif