Commit | Line | Data |
---|---|---|
1c1a9fa1 | 1 | /* |
5124214b | 2 | * userv service (or standalone program) for per-user IP subranges. |
3 | * | |
4 | * When invoked appropriately, it creates a point-to-point network | |
5 | * interface with specified parameters. It arranges for packets sent out | |
6 | * via that interface by the kernel to appear on its own stdout in SLIP or | |
7 | * CSLIP encoding, and packets injected into its own stdin to be given to | |
8 | * the kernel as if received on that interface. Optionally, additional | |
9 | * routes can be set up to arrange for traffic for other address ranges to | |
10 | * be routed through the new interface. | |
11 | * | |
12 | * This is the service program, which is invoked as root from userv (or may | |
13 | * be invoked firectly). | |
14 | * | |
15 | * Its arguments are supposed to be, in order, as follows: | |
16 | * | |
17 | * The first two arguments are usually supplied by the userv | |
18 | * configuration. See the file `ipif/ipif' in the source tree, which | |
19 | * is installed in /etc/userv/services.d/ipif by `make install': | |
1c1a9fa1 | 20 | * |
1e963473 | 21 | * <config> |
5124214b | 22 | * |
23 | * Specifies address ranges and gids which own them. The default | |
24 | * configuration supplies /etc/userv/ipif-networks, which is then read | |
25 | * for a list of entries, one per line. | |
26 | * | |
27 | * -- | |
28 | * Serves to separate the user-supplied and therefore untrusted | |
29 | * arguments from the trusted first argument. | |
30 | * | |
31 | * The remaining arguments are supplied by the (untrusted) caller: | |
32 | * | |
1c1a9fa1 | 33 | * <local-addr>,<peer-addr>,<mtu>,<proto> |
5124214b | 34 | * |
22410bfa IJ |
35 | * As for slattach. The only supported protocol is slip. |
36 | * Alternatively, set to `debug' to print debugging info and | |
37 | * exit. <local-addr> is address of the interface to be created | |
5124214b | 38 | * on the local system; <peer-addr> is the address of the |
39 | * point-to-point peer. They must be actual addresses (not | |
40 | * hostnames). | |
41 | * | |
1c1a9fa1 | 42 | * <prefix>/<mask>,<prefix>/<mask>,... |
5124214b | 43 | * |
44 | * List of additional routes to add for this interface. routes will | |
45 | * be set up on the local system arranging for packets for those | |
46 | * networks to be sent via the created interface. <prefix> must be an | |
47 | * IPv4 address, and mask must be an integer (dotted-quad masks are | |
48 | * not supported). If no additional routes are to be set up, use `-' | |
49 | * or supply an empty argument. | |
50 | * | |
447f36af IJ |
51 | * Each <config> item - whether a line in a file such as |
52 | * /etc/userv/ipif-networks, or the single trusted argument supplied | |
53 | * on the service program command line - is one of: | |
5124214b | 54 | * |
55 | * /<config-file-name> | |
56 | * ./<config-file-name> | |
57 | * ../<config-file-name> | |
58 | * | |
59 | * Reads a file which contains lines which are each <config> | |
60 | * items. | |
61 | * | |
e6a01344 | 62 | * <gid>,[=][-|+]<prefix>/<len>(-|+<prefix>/<len>...)[,<junk>] |
5124214b | 63 | * |
64 | * Indicates that <gid> may allocate addresses in the relevant address | |
65 | * range (<junk> is ignored). <gid> must be numeric. To specify a | |
66 | * single host address, you must specify a mask of /32. If `=' is | |
67 | * specified then the specific subrange is only allowed for the local | |
68 | * endpoint address, but not for remote addresses. | |
69 | * | |
e6a01344 | 70 | * More than one range may be given, with each range prefixed |
71 | * by + or -. In this case each address range in the rule will | |
72 | * scanned in order, and the first range in the rule that matches | |
73 | * any desired rule will count: if that first matching range is | |
74 | * prefixed by `+' (or nothing) then the rule applies, if it | |
75 | * is prefixed by `-' (or nothing matches), the rule does not. | |
76 | * | |
5124214b | 77 | * * |
78 | * Means that anything is to be permitted. This should not appear in | |
79 | * /etc/userv/ipif-networks, as that would permit any user on the | |
80 | * system to create any interfaces with any addresses and routes | |
81 | * attached. It is provided so that root can usefully invoke the ipif | |
82 | * service program directly (not via userv), without needing to set up | |
83 | * permissions in /etc/userv/ipif-networks. | |
84 | * | |
85 | * #... | |
86 | * | |
87 | * Comment. Blank lines are also ignored. | |
88 | * | |
89 | * NB: Permission is granted if _any_ config entry matches the request. | |
90 | * | |
91 | * The service program should be run from userv with no-disconnect-hup. | |
1c1a9fa1 | 92 | */ |
caa68336 | 93 | /* |
c07be359 | 94 | * Copyright (C) 1999-2000,2003 Ian Jackson |
95 | * This file is part of ipif, part of userv-utils | |
caa68336 | 96 | * |
97 | * This is free software; you can redistribute it and/or modify it | |
98 | * under the terms of the GNU General Public License as published by | |
99 | * the Free Software Foundation; either version 2 of the License, or | |
100 | * (at your option) any later version. | |
101 | * | |
102 | * This program is distributed in the hope that it will be useful, but | |
103 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
104 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
105 | * General Public License for more details. | |
106 | * | |
107 | * You should have received a copy of the GNU General Public License | |
108 | * along with userv-utils; if not, write to the Free Software | |
109 | * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
110 | * | |
111 | * $Id$ | |
112 | */ | |
1c1a9fa1 | 113 | |
114 | #include <stdio.h> | |
115 | #include <string.h> | |
116 | #include <stdlib.h> | |
117 | #include <assert.h> | |
118 | #include <errno.h> | |
1e963473 | 119 | #include <stdarg.h> |
120 | #include <ctype.h> | |
121 | #include <limits.h> | |
02b2392d | 122 | #include <signal.h> |
123 | #include <unistd.h> | |
22fd0ebe IJ |
124 | #include <stdint.h> |
125 | #include <poll.h> | |
02b2392d | 126 | |
127 | #include <sys/types.h> | |
128 | #include <sys/wait.h> | |
129 | #include <sys/stat.h> | |
1c1a9fa1 | 130 | |
22fd0ebe IJ |
131 | #include <sys/types.h> |
132 | #include <sys/ioctl.h> | |
133 | #include <sys/socket.h> | |
134 | ||
135 | #include <sys/stat.h> | |
136 | #include <fcntl.h> | |
137 | ||
138 | #include <linux/if.h> | |
139 | #include <linux/if_tun.h> | |
140 | ||
1e963473 | 141 | #define NARGS 4 |
415964dd | 142 | #define MAXEXROUTES 50 |
a48a8f0d | 143 | #define ATXTLEN 16 |
1c1a9fa1 | 144 | |
1e963473 | 145 | static const unsigned long gidmaxval= (unsigned long)((gid_t)-2); |
0ae8e686 | 146 | static const char *const protos_ok[]= { "slip", 0 }; |
02b2392d | 147 | static const int signals[]= { SIGHUP, SIGINT, SIGTERM, 0 }; |
1e963473 | 148 | |
149 | static const char *configstr, *proto; | |
1c1a9fa1 | 150 | static unsigned long localaddr, peeraddr, mtu; |
e6a01344 | 151 | static int localpming, peerpming; |
1e963473 | 152 | static int localallow, peerallow, allallow; |
1c1a9fa1 | 153 | static int nexroutes; |
154 | static struct exroute { | |
155 | unsigned long prefix, mask; | |
e6a01344 | 156 | int allow, pming; |
1c1a9fa1 | 157 | char prefixtxt[ATXTLEN], masktxt[ATXTLEN]; |
158 | } exroutes[MAXEXROUTES]; | |
159 | ||
1e963473 | 160 | static char localtxt[ATXTLEN]; |
161 | static char peertxt[ATXTLEN]; | |
162 | ||
163 | static struct pplace { | |
164 | struct pplace *parent; | |
165 | const char *filename; | |
166 | int lineno; | |
167 | } *cpplace; | |
168 | ||
02b2392d | 169 | |
22fd0ebe IJ |
170 | static int tunfd; |
171 | static char *ifname; | |
02b2392d | 172 | |
173 | ||
174 | static void terminate(int estatus) { | |
02b2392d | 175 | exit(estatus); |
176 | } | |
177 | ||
178 | ||
1e963473 | 179 | static void fatal(const char *fmt, ...) |
180 | __attribute__((format(printf,1,2))); | |
181 | static void fatal(const char *fmt, ...) { | |
182 | va_list al; | |
183 | va_start(al,fmt); | |
1c1a9fa1 | 184 | |
1e963473 | 185 | fputs("userv-ipif service: fatal error: ",stderr); |
186 | vfprintf(stderr, fmt, al); | |
187 | putc('\n',stderr); | |
02b2392d | 188 | terminate(8); |
1c1a9fa1 | 189 | } |
190 | ||
1e963473 | 191 | static void sysfatal(const char *fmt, ...) |
192 | __attribute__((format(printf,1,2))); | |
193 | static void sysfatal(const char *fmt, ...) { | |
194 | va_list al; | |
195 | int e; | |
196 | ||
197 | e= errno; | |
198 | va_start(al,fmt); | |
199 | ||
6d360a53 | 200 | fputs("userv-ipif service: fatal system error: ",stderr); |
1e963473 | 201 | vfprintf(stderr, fmt, al); |
6d360a53 | 202 | fprintf(stderr,": %s\n", strerror(e)); |
02b2392d | 203 | terminate(12); |
1c1a9fa1 | 204 | } |
1e963473 | 205 | |
206 | ||
207 | static void badusage(const char *fmt, ...) | |
208 | __attribute__((format(printf,1,2))); | |
209 | static void badusage(const char *fmt, ...) { | |
210 | va_list al; | |
211 | struct pplace *cpp; | |
1c1a9fa1 | 212 | |
1e963473 | 213 | if (cpplace) { |
214 | fprintf(stderr, | |
215 | "userv-ipif service: %s:%d: ", | |
216 | cpplace->filename, cpplace->lineno); | |
217 | } else { | |
218 | fputs("userv-ipif service: invalid usage: ",stderr); | |
219 | } | |
220 | va_start(al,fmt); | |
221 | vfprintf(stderr, fmt, al); | |
222 | putc('\n',stderr); | |
223 | ||
224 | if (cpplace) { | |
225 | for (cpp=cpplace->parent; cpp; cpp=cpp->parent) { | |
226 | fprintf(stderr, | |
227 | "userv-ipif service: %s:%d: ... in file included from here\n", | |
228 | cpp->filename, cpp->lineno); | |
229 | } | |
230 | } | |
02b2392d | 231 | terminate(16); |
1c1a9fa1 | 232 | } |
233 | ||
234 | static char *ip2txt(unsigned long addr, char *buf) { | |
235 | sprintf(buf, "%lu.%lu.%lu.%lu", | |
236 | (addr>>24) & 0x0ff, | |
237 | (addr>>16) & 0x0ff, | |
238 | (addr>>8) & 0x0ff, | |
239 | (addr) & 0x0ff); | |
240 | return buf; | |
241 | } | |
242 | ||
243 | static unsigned long eat_number(const char **argp, const char *what, | |
244 | unsigned long min, unsigned long max, | |
245 | const char *endchars, int *endchar_r) { | |
246 | /* If !endchars then the endchar must be a nul, otherwise it may be | |
247 | * a nul (resulting in *argp set to 0) or something else (*argp set | |
248 | * to point to after delim, *endchar_r set to delim). | |
249 | * *endchar_r may be 0. | |
250 | */ | |
251 | unsigned long rv; | |
252 | char *ep; | |
253 | int endchar; | |
254 | ||
baba1099 | 255 | if (!*argp) { badusage("missing number %s",what); } |
1c1a9fa1 | 256 | rv= strtoul(*argp,&ep,0); |
257 | if ((endchar= *ep)) { | |
baba1099 | 258 | if (!endchars) badusage("junk after number %s",what); |
1e963473 | 259 | if (!strchr(endchars,endchar)) |
260 | badusage("invalid character or delimiter `%c' in or after number, %s:" | |
baba1099 | 261 | " expected %s (or none?)", endchar,what,endchars); |
1c1a9fa1 | 262 | *argp= ep+1; |
263 | } else { | |
264 | *argp= 0; | |
265 | } | |
266 | if (endchar_r) *endchar_r= endchar; | |
1e963473 | 267 | if (rv < min || rv > max) badusage("number %s value %lu out of range %lu..%lu", |
268 | what, rv, min, max); | |
1c1a9fa1 | 269 | return rv; |
270 | } | |
271 | ||
e6a01344 | 272 | static int addrnet_overlap(unsigned long p1, unsigned long m1, |
273 | unsigned long p2, unsigned long m2) { | |
1c1a9fa1 | 274 | unsigned long mask; |
275 | ||
276 | mask= m1&m2; | |
e6a01344 | 277 | return (p1 & mask) == (p2 & mask); |
278 | } | |
279 | ||
280 | static void addrnet_mustdiffer(const char *w1, unsigned long p1, unsigned long m1, | |
281 | const char *w2, unsigned long p2, unsigned long m2) { | |
282 | if (!addrnet_overlap(p1,m1,p2,m2)) return; | |
1e963473 | 283 | badusage("%s %08lx/%08lx overlaps/clashes with %s %08lx/%08lx", |
284 | w1,p1,m1, w2,p2,m2); | |
1c1a9fa1 | 285 | } |
286 | ||
287 | static unsigned long eat_addr(const char **argp, const char *what, | |
1c1a9fa1 | 288 | const char *endchars, int *endchar_r) { |
289 | char whatbuf[100]; | |
290 | unsigned long rv; | |
291 | int i; | |
292 | ||
1c1a9fa1 | 293 | for (rv=0, i=0; |
294 | i<4; | |
295 | i++) { | |
296 | rv <<= 8; | |
297 | sprintf(whatbuf,"%s byte #%d",what,i); | |
298 | rv |= eat_number(argp,whatbuf, 0,255, i<3 ? "." : endchars, endchar_r); | |
299 | } | |
300 | ||
1c1a9fa1 | 301 | return rv; |
302 | } | |
303 | ||
304 | static void eat_prefixmask(const char **argp, const char *what, | |
1c1a9fa1 | 305 | const char *endchars, int *endchar_r, |
306 | unsigned long *prefix_r, unsigned long *mask_r, int *len_r) { | |
307 | /* mask_r and len_r may be 0 */ | |
308 | char whatbuf[100]; | |
309 | int len; | |
310 | unsigned long prefix, mask; | |
311 | ||
1e963473 | 312 | prefix= eat_addr(argp,what, "/",0); |
1c1a9fa1 | 313 | sprintf(whatbuf,"%s length",what); |
314 | len= eat_number(argp,whatbuf, 0,32, endchars,endchar_r); | |
315 | ||
f56780b7 | 316 | mask= len ? (~0UL << (32-len)) : 0UL; |
baba1099 | 317 | if (prefix & ~mask) badusage("%s prefix %08lx not fully contained in mask %08lx", |
1e963473 | 318 | what,prefix,mask); |
1c1a9fa1 | 319 | *prefix_r= prefix; |
320 | if (mask_r) *mask_r= mask; | |
321 | if (len_r) *len_r= len; | |
322 | } | |
1e963473 | 323 | |
324 | static int addrnet_isin(unsigned long prefix, unsigned long mask, | |
325 | unsigned long mprefix, unsigned long mmask) { | |
326 | return !(~mask & mmask) && (prefix & mmask) == mprefix; | |
327 | } | |
e6a01344 | 328 | |
329 | /* Totally hideous algorithm for parsing the config file lines. | |
330 | * For each config file line, we first see if its gid applies. If not | |
331 | * we skip it. Otherwise, we do | |
332 | * permit_begin | |
333 | * which sets <foo>pming to 1 | |
334 | * for each range. <foo>pming may be 0 if we've determined that | |
335 | * this line does not apply to <foo>. | |
336 | * permit_range | |
337 | * which calls permit_range_thing for each <foo> | |
338 | * which checks to see if <foo> is inside the relevant | |
339 | * range (for +) or overlaps it (for -) and updates | |
340 | * <foo>allow and <foo>pming. | |
341 | */ | |
342 | ||
343 | static void permit_begin(void) { | |
344 | int i; | |
1c1a9fa1 | 345 | |
e6a01344 | 346 | localpming= peerpming= 1; |
347 | for (i=0; i<nexroutes; i++) exroutes[i].pming= 1; | |
348 | } | |
349 | ||
350 | static void permit_range_thing(unsigned long tprefix, unsigned long tmask, | |
351 | const char *what, int *tallow, int *tpming, | |
352 | unsigned long pprefix, unsigned long pmask, | |
353 | int plus, int *any) { | |
354 | if (plus) { | |
355 | if (!addrnet_isin(tprefix,tmask, pprefix,pmask)) return; | |
356 | if (*tpming) *tallow= 1; | |
357 | } else { | |
358 | if (!addrnet_overlap(tprefix,tmask, pprefix,pmask)) return; | |
359 | *tpming= 0; | |
360 | } | |
361 | if (!proto) printf(" %c%s", plus?'+':'-', what); | |
362 | *any= 1; | |
363 | } | |
1c1a9fa1 | 364 | |
e6a01344 | 365 | static void permit_range(unsigned long prefix, unsigned long mask, |
366 | int plus, int localonly) { | |
1e963473 | 367 | int i, any; |
e6a01344 | 368 | char idbuf[40]; |
1c1a9fa1 | 369 | |
e6a01344 | 370 | assert(!(prefix & ~mask)); |
2c310400 | 371 | any= 0; |
1c1a9fa1 | 372 | |
e6a01344 | 373 | permit_range_thing(localaddr,~0UL,"local", &localallow,&localpming, |
374 | prefix,mask, plus,&any); | |
375 | ||
0f91e874 | 376 | if (!localonly) { |
e6a01344 | 377 | permit_range_thing(peeraddr,~0UL, "peer-addr", &peerallow,&peerpming, |
378 | prefix,mask, plus,&any); | |
0f91e874 | 379 | for (i=0; i<nexroutes; i++) { |
e6a01344 | 380 | sprintf(idbuf,"route#%d",i); |
381 | permit_range_thing(exroutes[i].prefix,exroutes[i].mask, idbuf, | |
382 | &exroutes[i].allow,&exroutes[i].pming, | |
383 | prefix,mask, plus,&any); | |
1e963473 | 384 | } |
385 | } | |
e6a01344 | 386 | if (!proto) |
a48a8f0d | 387 | if (!any) fputs(" nothing",stdout); |
1e963473 | 388 | } |
1c1a9fa1 | 389 | |
1e963473 | 390 | static void pconfig(const char *configstr, int truncated); |
391 | ||
392 | static void pfile(const char *filename) { | |
393 | FILE *file; | |
394 | char buf[PATH_MAX]; | |
395 | int l, truncated, c; | |
396 | struct pplace npp, *cpp; | |
397 | ||
398 | for (cpp=cpplace; cpp; cpp=cpp->parent) { | |
399 | if (!strcmp(cpp->filename,filename)) | |
400 | badusage("recursive configuration file `%s'",filename); | |
401 | } | |
402 | ||
403 | file= fopen(filename,"r"); | |
404 | if (!file) | |
405 | badusage("cannot open configuration file `%s': %s", filename, strerror(errno)); | |
406 | ||
407 | if (!proto) printf("config file `%s':\n",filename); | |
408 | ||
409 | npp.parent= cpplace; | |
410 | npp.filename= filename; | |
411 | npp.lineno= 0; | |
412 | cpplace= &npp; | |
413 | ||
414 | while (fgets(buf, sizeof(buf), file)) { | |
415 | npp.lineno++; | |
416 | l= strlen(buf); | |
417 | if (!l) continue; | |
418 | ||
419 | truncated= (buf[l-1] != '\n'); | |
420 | while (l>0 && isspace((unsigned char) buf[l-1])) l--; | |
421 | if (!l) continue; | |
422 | buf[l]= 0; | |
423 | ||
424 | if (truncated) { | |
425 | while ((c= getc(file)) != EOF && c != '\n'); | |
426 | if (c == EOF) break; | |
427 | } | |
428 | ||
429 | pconfig(buf,truncated); | |
430 | } | |
431 | if (ferror(file)) | |
432 | badusage("failed while reading configuration file: %s", strerror(errno)); | |
433 | ||
434 | cpplace= npp.parent; | |
435 | } | |
436 | ||
437 | static void pconfig(const char *configstr, int truncated) { | |
438 | unsigned long fgid, tgid, pprefix, pmask; | |
e6a01344 | 439 | int plen, localonly, plus, rangeix, delim; |
1e963473 | 440 | char ptxt[ATXTLEN]; |
e6a01344 | 441 | char whattxt[100]; |
1e963473 | 442 | const char *gidlist; |
443 | ||
444 | switch (configstr[0]) { | |
445 | case '*': | |
e6a01344 | 446 | permit_begin(); |
447 | permit_range(0UL,0UL,1,0); | |
1e963473 | 448 | return; |
449 | ||
450 | case '#': | |
451 | return; | |
452 | ||
453 | case '/': case '.': | |
454 | if (truncated) badusage("filename too long (`%.100s...')",configstr); | |
455 | pfile(configstr); | |
456 | return; | |
457 | ||
458 | default: | |
459 | if (!isdigit((unsigned char)configstr[0])) | |
460 | badusage("unknown configuration directive"); | |
461 | ||
462 | fgid= eat_number(&configstr,"gid", 0,gidmaxval, ",",0); | |
1e963473 | 463 | |
e6a01344 | 464 | if (!proto) printf(" %5lu", fgid); |
1e963473 | 465 | |
466 | gidlist= getenv("USERV_GID"); | |
467 | if (!gidlist) fatal("USERV_GID not set"); | |
1c1a9fa1 | 468 | for (;;) { |
1e963473 | 469 | if (!gidlist) { |
e6a01344 | 470 | if (!proto) printf(" no matching gid\n"); |
1e963473 | 471 | return; |
1c1a9fa1 | 472 | } |
1e963473 | 473 | tgid= eat_number(&gidlist,"userv-gid", 0,gidmaxval, " ",0); |
474 | if (tgid == fgid) break; | |
1c1a9fa1 | 475 | } |
e6a01344 | 476 | |
477 | if (configstr[0] == '=') { | |
478 | localonly= 1; | |
479 | configstr++; | |
480 | } else { | |
481 | localonly= 0; | |
482 | } | |
483 | ||
484 | permit_begin(); | |
485 | ||
486 | rangeix= 0; | |
487 | plus= 1; | |
488 | switch (configstr[0]) { | |
489 | case '-': plus= 0; /* fall through */ | |
490 | case '+': configstr++; | |
491 | default:; | |
492 | } | |
493 | ||
494 | for (;;) { | |
495 | sprintf(whattxt, "%s-prefix#%d", | |
496 | plus ? "permitted" : "notpermitted", | |
497 | rangeix); | |
498 | eat_prefixmask(&configstr,whattxt, ",+-",&delim, | |
499 | &pprefix,&pmask,&plen); | |
500 | if (!configstr && truncated) | |
501 | badusage("gid,prefix/len,... spec too long"); | |
502 | ||
503 | if (!proto) | |
504 | printf(" %c%s/%d:", plus?'+':'-',ip2txt(pprefix,ptxt), plen); | |
505 | ||
506 | permit_range(pprefix,pmask,plus,localonly); | |
507 | if (delim==',') break; | |
508 | ||
509 | plus= delim=='-' ? 0 : 1; | |
510 | rangeix++; | |
511 | } | |
512 | ||
513 | putchar('\n'); | |
1e963473 | 514 | return; |
1c1a9fa1 | 515 | } |
1e963473 | 516 | } |
517 | ||
518 | static void checkallow(int allow, const char *what, | |
519 | const char *prefixtxt, const char *masktxt) { | |
520 | if (allow) return; | |
521 | fprintf(stderr,"userv-ipif service: access denied for %s, %s/%s\n", | |
522 | what, prefixtxt, masktxt); | |
523 | allallow= 0; | |
524 | } | |
525 | ||
526 | static void parseargs(int argc, const char *const *argv) { | |
527 | unsigned long routeaddr, routemask; | |
528 | const char *carg; | |
529 | const char *const *cprotop; | |
530 | int i; | |
531 | char erwhatbuf[100], erwhatbuf2[100]; | |
532 | ||
533 | if (argc < NARGS+1) { badusage("too few arguments"); } | |
534 | if (argc > NARGS+1) { badusage("too many arguments"); } | |
535 | ||
536 | configstr= *++argv; | |
1c1a9fa1 | 537 | |
538 | carg= *++argv; | |
1e963473 | 539 | if (strcmp(carg,"--")) badusage("separator argument `--' not found, got `%s'",carg); |
1c1a9fa1 | 540 | |
1e963473 | 541 | carg= *++argv; |
542 | localaddr= eat_addr(&carg,"local-addr", ",",0); | |
543 | peeraddr= eat_addr(&carg,"peer-addr", ",",0); | |
1c1a9fa1 | 544 | mtu= eat_number(&carg,"mtu", 576,65536, ",",0); |
1e963473 | 545 | localallow= peerallow= 0; |
1c1a9fa1 | 546 | |
547 | if (!strcmp(carg,"debug")) { | |
548 | proto= 0; | |
549 | } else { | |
550 | for (cprotop= protos_ok; | |
551 | (proto= *cprotop) && strcmp(proto,carg); | |
552 | cprotop++); | |
553 | if (!proto) fatal("invalid protocol"); | |
554 | } | |
555 | ||
556 | addrnet_mustdiffer("local-addr",localaddr,~0UL, "peer-addr",peeraddr,~0UL); | |
557 | ||
558 | carg= *++argv; | |
2ed30784 | 559 | if (strcmp(carg,"-")) { |
560 | for (nexroutes=0; | |
561 | carg && *carg; | |
562 | nexroutes++) { | |
563 | if (nexroutes == MAXEXROUTES) | |
564 | fatal("too many extra routes (only %d allowed)",MAXEXROUTES); | |
565 | sprintf(erwhatbuf,"route#%d",nexroutes); | |
1c1a9fa1 | 566 | |
2ed30784 | 567 | eat_prefixmask(&carg,erwhatbuf, ",",0, &routeaddr,&routemask,0); |
568 | if (routemask == ~0UL) { | |
569 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "local-addr",localaddr,~0UL); | |
570 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "peer-addr",peeraddr,~0UL); | |
571 | } | |
572 | for (i=0; i<nexroutes; i++) { | |
573 | sprintf(erwhatbuf2,"route#%d",i); | |
574 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, | |
575 | erwhatbuf2,exroutes[i].prefix,exroutes[i].mask); | |
576 | } | |
577 | exroutes[nexroutes].prefix= routeaddr; | |
578 | exroutes[nexroutes].mask= routemask; | |
579 | exroutes[nexroutes].allow= 0; | |
580 | ip2txt(routeaddr,exroutes[nexroutes].prefixtxt); | |
581 | ip2txt(routemask,exroutes[nexroutes].masktxt); | |
1c1a9fa1 | 582 | } |
1c1a9fa1 | 583 | } |
2ed30784 | 584 | |
1c1a9fa1 | 585 | ip2txt(localaddr,localtxt); |
586 | ip2txt(peeraddr,peertxt); | |
1e963473 | 587 | } |
588 | ||
589 | static void checkpermit(void) { | |
590 | int i; | |
591 | char erwhatbuf[100]; | |
1c1a9fa1 | 592 | |
1e963473 | 593 | allallow= 1; |
594 | checkallow(localallow,"local-addr", localtxt,"32"); | |
595 | checkallow(peerallow,"peer-addr", peertxt,"32"); | |
596 | for (i=0; i<nexroutes; i++) { | |
597 | sprintf(erwhatbuf, "route#%d", i); | |
598 | checkallow(exroutes[i].allow, erwhatbuf, exroutes[i].prefixtxt, exroutes[i].masktxt); | |
599 | } | |
600 | if (!allallow) fatal("access denied"); | |
601 | } | |
602 | ||
603 | static void dumpdebug(void) __attribute__((noreturn)); | |
604 | static void dumpdebug(void) { | |
605 | int i; | |
606 | char erwhatbuf[100]; | |
607 | ||
608 | printf("protocol: debug\n" | |
609 | "local: %08lx == %s\n" | |
610 | "peer: %08lx == %s\n" | |
611 | "mtu: %ld\n" | |
612 | "routes: %d\n", | |
613 | localaddr, localtxt, | |
614 | peeraddr, peertxt, | |
615 | mtu, | |
616 | nexroutes); | |
617 | for (i=0; i<nexroutes; i++) { | |
618 | sprintf(erwhatbuf, "route#%d:", i); | |
619 | printf("%-9s %08lx/%08lx == %s/%s\n", | |
620 | erwhatbuf, | |
621 | exroutes[i].prefix, exroutes[i].mask, | |
622 | exroutes[i].prefixtxt, exroutes[i].masktxt); | |
1c1a9fa1 | 623 | } |
1e963473 | 624 | if (ferror(stdout) || fclose(stdout)) sysfatal("flush stdout"); |
625 | exit(0); | |
626 | } | |
627 | ||
6d360a53 | 628 | |
22fd0ebe IJ |
629 | static int task(const char *desc) { |
630 | pid_t pid, pidr; | |
631 | int status; | |
02b2392d | 632 | |
633 | pid= fork(); | |
6d360a53 | 634 | if (pid == (pid_t)-1) sysfatal("fork for task"); |
22fd0ebe | 635 | if (!pid) return 1; |
6d360a53 | 636 | |
637 | for (;;) { | |
0ae8e686 | 638 | pidr= waitpid(pid,&status,0); |
22fd0ebe IJ |
639 | if (pidr!=(pid_t)-1) break; |
640 | if (errno==EINTR) continue; | |
641 | sysfatal("waitpid for task"); | |
6d360a53 | 642 | } |
22fd0ebe | 643 | assert(pidr==pid); |
6d360a53 | 644 | |
22fd0ebe | 645 | if (WIFEXITED(status)) { |
0ae8e686 IJ |
646 | if (WEXITSTATUS(status)) |
647 | fatal("userv-ipif service: %s exited with error exit status %d\n", | |
22fd0ebe IJ |
648 | desc, WEXITSTATUS(status)); |
649 | } else if (WIFSIGNALED(status)) { | |
0ae8e686 IJ |
650 | fatal("userv-ipif service: %s died due to signal %s%s\n", |
651 | desc, strsignal(WTERMSIG(status)), | |
652 | WCOREDUMP(status) ? " (core dumped)" : ""); | |
22fd0ebe | 653 | } else { |
0ae8e686 IJ |
654 | fatal("userv-ipif service: %s unexpectedly terminated" |
655 | " with unknown status code %d\n", desc, status); | |
02b2392d | 656 | } |
657 | ||
22fd0ebe | 658 | return 0; |
02b2392d | 659 | } |
660 | ||
22fd0ebe IJ |
661 | static void createif(void) { |
662 | static const char ifnamepat[]= "userv%d"; | |
663 | struct ifreq ifr; | |
02b2392d | 664 | int r; |
02b2392d | 665 | |
22fd0ebe IJ |
666 | memset(&ifr,0,sizeof(ifr)); |
667 | ifr.ifr_flags= IFF_TUN | IFF_NO_PI; | |
6d360a53 | 668 | |
22fd0ebe IJ |
669 | assert(sizeof(ifr.ifr_name) >= sizeof(ifnamepat)); |
670 | strcpy(ifr.ifr_name, ifnamepat); | |
02b2392d | 671 | |
22fd0ebe IJ |
672 | tunfd= open("/dev/net/tun", O_RDWR); |
673 | if (!tunfd) sysfatal("open /dev/net/tun"); | |
02b2392d | 674 | |
22fd0ebe IJ |
675 | r= fcntl(tunfd, F_GETFD); |
676 | if (r==-1) sysfatal("fcntl(tunfd,F_GETFD)"); | |
677 | r= fcntl(tunfd, F_SETFD, r|FD_CLOEXEC); | |
678 | if (r==-1) sysfatal("fcntl(tunfd,F_SETFD,|FD_CLOEXEC)"); | |
02b2392d | 679 | |
22fd0ebe IJ |
680 | r= ioctl(tunfd, TUNSETIFF, (void*)&ifr); |
681 | if (r) sysfatal("ioctl TUNSETIFF"); | |
682 | ||
683 | /* ifr.ifr_name might not be null-terminated. crazy abi. */ | |
684 | ifname= malloc(sizeof(ifr.ifr_name)+1); | |
685 | if (!ifname) sysfatal("malloc for interface name"); | |
686 | memcpy(ifname, ifr.ifr_name, sizeof(ifr.ifr_name)); | |
687 | ifname[sizeof(ifr.ifr_name)]= 0; | |
02b2392d | 688 | } |
689 | ||
690 | static void netconfigure(void) { | |
691 | char mtutxt[100]; | |
692 | int i; | |
693 | ||
22fd0ebe | 694 | if (task("ifconfig")) { |
02b2392d | 695 | sprintf(mtutxt,"%lu",mtu); |
696 | ||
697 | execlp("ifconfig", "ifconfig", ifname, localtxt, | |
0ae8e686 | 698 | "netmask","255.255.255.255", "pointopoint",peertxt, "-broadcast", |
02b2392d | 699 | "mtu",mtutxt, "up", (char*)0); |
700 | sysfatal("cannot exec ifconfig"); | |
701 | } | |
702 | ||
02b2392d | 703 | for (i=0; i<nexroutes; i++) { |
22fd0ebe | 704 | if (task("route")) { |
6d360a53 | 705 | execlp("route","route", "add", "-net",exroutes[i].prefixtxt, |
02b2392d | 706 | "netmask",exroutes[i].masktxt, |
707 | "gw",peertxt, "dev",ifname, (char*)0); | |
708 | sysfatal("cannot exec route (for route)"); | |
709 | } | |
710 | } | |
711 | } | |
712 | ||
22fd0ebe | 713 | static void setnonblock(int fd) { |
5f1c67ff | 714 | int r; |
22fd0ebe IJ |
715 | r= fcntl(fd,F_GETFL); |
716 | if (r==-1) sysfatal("fcntl F_GETFL"); | |
717 | r= fcntl(fd,F_SETFL, r|O_NONBLOCK); | |
718 | if (r==-1) sysfatal("fcntl F_SETFL O_NONBLOCK"); | |
719 | } | |
720 | ||
721 | static void rx_packet(const uint8_t *packet, int len) { | |
03203c8a IJ |
722 | if (!len) |
723 | return; | |
22fd0ebe IJ |
724 | for (;;) { |
725 | int r= write(tunfd, packet, len); | |
726 | if (r<0) { | |
727 | if (errno==EINTR) continue; | |
03203c8a | 728 | if (errno==EAGAIN || errno==ENOMEM) return; /* oh well */ |
22fd0ebe IJ |
729 | sysfatal("error writing packet to tun (transmitting)"); |
730 | } | |
731 | assert(r==len); | |
732 | return; | |
733 | } | |
734 | } | |
735 | ||
736 | static int output_waiting, input_waiting; | |
737 | ||
738 | #define SLIP_END 0300 | |
739 | #define SLIP_ESC 0333 | |
740 | #define SLIP_ESC_END 0334 | |
741 | #define SLIP_ESC_ESC 0335 | |
742 | ||
743 | static void more_rx_data(uint8_t *input_buf, uint8_t *output_buf) { | |
744 | /* we make slip_data never contain continuation of a packet */ | |
745 | /* input_buf is passed as a parameter since it's in copydata's stack frame */ | |
746 | static int scanned; | |
747 | static int output_len; | |
748 | ||
749 | uint8_t *op= output_buf + output_len; | |
750 | const uint8_t *ip= input_buf + scanned; | |
751 | const uint8_t *ip_end= input_buf + input_waiting; | |
752 | int eaten= 0; | |
5f1c67ff | 753 | |
5f1c67ff | 754 | for (;;) { |
22fd0ebe IJ |
755 | if (ip>=ip_end) break; |
756 | uint8_t c= *ip++; | |
757 | if (c==SLIP_END) { | |
758 | rx_packet(output_buf, op-output_buf); | |
759 | op= output_buf; | |
760 | eaten= ip - input_buf; | |
761 | continue; | |
762 | } | |
763 | if (c==SLIP_ESC) { | |
764 | if (ip>=ip_end) { /* rescan this when there's more */ ip--; break; } | |
765 | c= *ip++; | |
766 | if (c==SLIP_ESC_END) c=SLIP_END; | |
767 | else if (c==SLIP_ESC_ESC) c=SLIP_ESC; | |
768 | else fatal("unexpected byte 0%o after SLIP_ESC",c); | |
769 | } | |
770 | if (op == output_buf+mtu) | |
771 | fatal("SLIP packet exceeds mtu"); | |
772 | *op++= c; | |
773 | } | |
774 | ||
775 | output_len= op - output_buf; | |
776 | scanned= ip - input_buf; | |
777 | ||
778 | input_waiting -= eaten; | |
779 | memmove(input_buf, input_buf+eaten, input_waiting); | |
780 | scanned -= eaten; | |
781 | } | |
782 | ||
783 | static void tx_packet(uint8_t *output_buf, const uint8_t *ip, int inlen) { | |
784 | /* output_buf is passed as a parameter since it's in copydata's stack frame */ | |
785 | assert(!output_waiting); | |
786 | uint8_t *op= output_buf; | |
787 | ||
788 | *op++= SLIP_END; | |
789 | while (inlen-- >0) { | |
790 | uint8_t c= *ip++; | |
791 | if (c==SLIP_END) { *op++= SLIP_ESC; *op++= SLIP_ESC_END; } | |
792 | else if (c==SLIP_ESC) { *op++= SLIP_ESC; *op++= SLIP_ESC_ESC; } | |
793 | else *op++= c; | |
5f1c67ff | 794 | } |
0ae8e686 IJ |
795 | *op++= SLIP_END; |
796 | assert(op <= output_buf + mtu*2+2); | |
797 | ||
22fd0ebe IJ |
798 | output_waiting= op - output_buf; |
799 | } | |
800 | ||
801 | static void copydata(void) __attribute__((noreturn)); | |
802 | static void copydata(void) { | |
803 | uint8_t output_buf[mtu*2+2]; | |
804 | uint8_t input_buf[mtu*2+2]; | |
805 | uint8_t rx_packet_buf[mtu]; | |
806 | ||
807 | int r, i; | |
02b2392d | 808 | |
22fd0ebe IJ |
809 | struct pollfd polls[3]; |
810 | memset(polls, 0, sizeof(polls)); | |
811 | ||
812 | polls[0].fd= 0; polls[0].events= POLLIN; | |
813 | polls[1].fd= 1; | |
814 | polls[2].fd= tunfd; | |
815 | ||
816 | /* We don't do flow control on input packets; instead, we just throw | |
817 | * away ones which the kernel doesn't accept. So we always poll for | |
818 | * those. | |
819 | * | |
820 | * Output packets we buffer, so we poll only as appropriate for those. | |
821 | */ | |
822 | ||
0ae8e686 IJ |
823 | /* Start by transmitting one END byte to say we're ready. */ |
824 | output_buf[0]= SLIP_END; | |
825 | output_waiting= 1; | |
826 | ||
22fd0ebe IJ |
827 | for (;;) { |
828 | if (output_waiting) { | |
829 | r= write(1, output_buf, output_waiting); | |
830 | if (r<0) { | |
831 | if (errno==EINTR) continue; | |
832 | if (errno!=EAGAIN) | |
833 | sysfatal("error writing SLIP output (packets being received)"); | |
834 | } else { | |
835 | assert(r>0); | |
836 | output_waiting -= r; | |
837 | memmove(output_buf, output_buf+r, output_waiting); | |
838 | } | |
839 | } | |
840 | if (output_waiting) { | |
841 | polls[1].events |= POLLOUT; | |
842 | polls[2].events &= ~POLLIN; | |
843 | } else { | |
844 | polls[1].events &= ~POLLOUT; | |
845 | polls[2].events |= POLLIN; | |
846 | } | |
847 | r= poll(polls,3,-1); | |
848 | ||
849 | if (r<0) { | |
850 | if (errno==EINTR) continue; | |
851 | sysfatal("poll() failed"); | |
852 | } | |
853 | assert(r>0); /* we used an infinite timeout */ | |
854 | ||
855 | for (i=0; i<sizeof(polls)/sizeof(polls[0]); i++) | |
856 | if (polls[i].revents & ~polls[i].events) | |
857 | fatal("unexpected revents 0x%x for fd=%d", | |
858 | polls[i].revents, polls[i].fd); | |
859 | ||
860 | if (polls[0].events & POLLIN) { | |
861 | int want= sizeof(input_buf) - input_waiting; | |
862 | if (want<0) fatal("incoming packet necessarily exceeds MTU"); | |
863 | r= read(0, input_buf + input_waiting, want); | |
864 | if (r>0) { | |
865 | input_waiting += r; | |
66b873f0 | 866 | assert(input_waiting <= sizeof(input_buf)); |
22fd0ebe IJ |
867 | more_rx_data(input_buf, rx_packet_buf); |
868 | } else if (r==0) { | |
869 | terminate(0); | |
870 | } else { | |
871 | if (!(errno==EINTR || errno==EAGAIN)) | |
872 | sysfatal("error reading input SLIP data (packets to transmit)"); | |
873 | } | |
874 | } | |
875 | ||
876 | /* We handle what would be (polls[1].events & POLLOUT) above, | |
877 | * unconditionally. That eliminates the need to poll in the usual case */ | |
878 | ||
879 | if (polls[2].events & POLLIN) { | |
880 | uint8_t packet_buf[mtu]; | |
881 | r= read(tunfd, packet_buf, mtu); | |
882 | if (r>0) { | |
883 | tx_packet(output_buf, packet_buf, r); | |
884 | } else { | |
885 | assert(r<0); | |
886 | if (!(errno==EAGAIN || errno==EWOULDBLOCK)) | |
887 | sysfatal("error reading packet (being transmitted) from tun"); | |
888 | } | |
889 | } | |
890 | } | |
02b2392d | 891 | } |
892 | ||
1e963473 | 893 | int main(int argc, const char *const *argv) { |
894 | parseargs(argc,argv); | |
895 | pconfig(configstr,0); | |
896 | checkpermit(); | |
897 | if (!proto) dumpdebug(); | |
898 | ||
22fd0ebe | 899 | createif(); |
02b2392d | 900 | netconfigure(); |
22fd0ebe IJ |
901 | setnonblock(tunfd); |
902 | setnonblock(0); | |
903 | setnonblock(1); | |
02b2392d | 904 | copydata(); |
1c1a9fa1 | 905 | } |