GNUnet 0.21.1
gnunet-helper-dns.c File Reference

helper to install firewall rules to hijack all DNS traffic and send it to our virtual interface (except for DNS traffic that originates on the specified port). More...

#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_common.h"
#include "gnunet_protocols.h"
Include dependency graph for gnunet-helper-dns.c:

Go to the source code of this file.

Data Structures

struct  in6_ifreq
 This is in linux/include/net/ipv6.h, but not always exported... More...
 

Macros

#define MAX_SIZE   65536
 Need 'struct GNUNET_MessageHeader'. More...
 
#define DNS_PORT   "53"
 Port for DNS traffic. More...
 
#define DNS_MARK   "136708149"
 Marker we set for our hijacked DNS traffic. More...
 
#define DNS_TABLE   "53"
 Table we use for our DNS rules. More...
 

Functions

static void signal_handler (int signal)
 Signal handler called to initiate "nice" shutdown. More...
 
static void open_dev_null (int target_fd, int flags)
 Open '/dev/null' and make the result the given file descriptor. More...
 
static int fork_and_exec (const char *file, char *const cmd[])
 Run the given command and wait for it to complete. More...
 
static int init_tun (char *dev)
 Creates a tun-interface called dev;. More...
 
static void set_address6 (const char *dev, const char *address, unsigned long prefix_len)
 Sets the IPv6-Address given in address on the interface dev. More...
 
static void set_address4 (const char *dev, const char *address, const char *mask)
 Sets the IPv4-Address given in address on the interface dev. More...
 
static void run (int fd_tun)
 Start forwarding to and from the tunnel. More...
 
int main (int argc, char *const *argv)
 Main function of "gnunet-helper-dns", which opens a VPN tunnel interface, redirects all outgoing DNS traffic (except from the specified port) to that interface and then passes traffic from and to the interface via stdin/stdout. More...
 

Variables

static const char * sbin_iptables
 Name and full path of IPTABLES binary. More...
 
static const char * sbin_ip6tables
 Name and full path of IPTABLES binary. More...
 
static const char * sbin_sysctl
 Name and full path of sysctl binary. More...
 
static const char * sbin_ip
 Name and full path of IPTABLES binary. More...
 
static int cpipe [2]
 Control pipe for shutdown via signal. More...
 

Detailed Description

helper to install firewall rules to hijack all DNS traffic and send it to our virtual interface (except for DNS traffic that originates on the specified port).

We then allow interacting with our virtual interface via stdin/stdout.

Author
Philipp Tölke
Christian Grothoff

This program alters the Linux firewall rules so that DNS traffic that ordinarily exits the system can be intercepted and managed by a virtual interface. In order to achieve this, DNS traffic is marked with the DNS_MARK given in below and re-routed to a custom table with the DNS_TABLE ID given below. Systems and administrators must take care to not cause conflicts with these values (it was deemed safest to hardcode them as passing these values as arguments might permit messing with arbitrary firewall rules, which would be dangerous). Traffic coming from the same group ID as the effective group ID that this process is running as is not intercepted.

The code first sets up the virtual interface, then begins to redirect the DNS traffic to it, and then on errors or SIGTERM shuts down the virtual interface and removes the rules for the traffic redirection.

Note that having this binary SUID is only partially safe: it will allow redirecting (and intercepting / mangling) of all DNS traffic originating from this system by any user who is able to run it. Furthermore, this code will make it trivial to DoS all DNS traffic originating from the current system, simply by sending it to nowhere (redirect stdout to /dev/null).

Naturally, neither of these problems can be helped as this is the fundamental purpose of the binary. Certifying that this code is "safe" thus only means that it doesn't allow anything else (such as local priv. escalation, etc.).

The following list of people have reviewed this code and considered it safe (within specifications) since the last modification (if you reviewed it, please have your name added to the list):

  • Christian Grothoff

Definition in file gnunet-helper-dns.c.

Macro Definition Documentation

◆ MAX_SIZE

#define MAX_SIZE   65536

Need 'struct GNUNET_MessageHeader'.

Need DNS message types. Maximum size of a GNUnet message (GNUNET_MAX_MESSAGE_SIZE)

Definition at line 86 of file gnunet-helper-dns.c.

◆ DNS_PORT

#define DNS_PORT   "53"

Port for DNS traffic.

Definition at line 123 of file gnunet-helper-dns.c.

◆ DNS_MARK

#define DNS_MARK   "136708149"

Marker we set for our hijacked DNS traffic.

We use GNUnet's port (2086) plus the DNS port (53) in HEX to make a 32-bit mark (which is hopefully long enough to not collide); so 0x08260035 = 136708149 (hopefully unique enough...).

Definition at line 131 of file gnunet-helper-dns.c.

◆ DNS_TABLE

#define DNS_TABLE   "53"

Table we use for our DNS rules.

0-255 is the range and 0, 253, 254 and 255 are already reserved. As this is about DNS and as "53" is likely (fingers crossed!) high enough to not usually conflict with a normal user's setup, we use 53 to give a hint that this has something to do with DNS.

Definition at line 140 of file gnunet-helper-dns.c.

Function Documentation

◆ signal_handler()

static void signal_handler ( int  signal)
static

Signal handler called to initiate "nice" shutdown.

Signals select loop via non-bocking pipe 'cpipe'.

Parameters
signalsignal number of the signal (not used)

Definition at line 157 of file gnunet-helper-dns.c.

158{
159 /* ignore return value, as the signal handler could theoretically
160 be called many times before the shutdown can actually happen */
161 (void) write (cpipe[1], "K", 1);
162}
static int cpipe[2]
Control pipe for shutdown via signal.

References cpipe.

Referenced by main().

Here is the caller graph for this function:

◆ open_dev_null()

static void open_dev_null ( int  target_fd,
int  flags 
)
static

Open '/dev/null' and make the result the given file descriptor.

Parameters
target_fddesired FD to point to /dev/null
flagsopen flags (O_RDONLY, O_WRONLY)

Definition at line 173 of file gnunet-helper-dns.c.

175{
176 int fd;
177
178 fd = open ("/dev/null", flags);
179 if (-1 == fd)
180 abort ();
181 if (fd == target_fd)
182 return;
183 if (-1 == dup2 (fd, target_fd))
184 {
185 (void) close (fd);
186 abort ();
187 }
188 (void) close (fd);
189}

Referenced by fork_and_exec().

Here is the caller graph for this function:

◆ fork_and_exec()

static int fork_and_exec ( const char *  file,
char *const  cmd[] 
)
static

Run the given command and wait for it to complete.

Parameters
filename of the binary to run
cmdcommand line arguments (as given to 'execv')
Returns
0 on success, 1 on any error

Definition at line 200 of file gnunet-helper-dns.c.

202{
203 int status;
204 pid_t pid;
205 pid_t ret;
206
207 pid = fork ();
208 if (-1 == pid)
209 {
210 fprintf (stderr,
211 "fork failed: %s\n",
212 strerror (errno));
213 return 1;
214 }
215 if (0 == pid)
216 {
217 /* we are the child process */
218 /* close stdin/stdout to not cause interference
219 with the helper's main protocol! */
220 (void) close (0);
221 open_dev_null (0, O_RDONLY);
222 (void) close (1);
223 open_dev_null (1, O_WRONLY);
224 (void) execv (file, cmd);
225 /* can only get here on error */
226 fprintf (stderr,
227 "exec `%s' failed: %s\n",
228 file,
229 strerror (errno));
230 _exit (1);
231 }
232 /* keep running waitpid as long as the only error we get is 'EINTR' */
233 while ((-1 == (ret = waitpid (pid, &status, 0))) &&
234 (errno == EINTR))
235 ;
236 if (-1 == ret)
237 {
238 fprintf (stderr,
239 "waitpid failed: %s\n",
240 strerror (errno));
241 return 1;
242 }
243 if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status))))
244 return 1;
245 /* child process completed and returned success, we're happy */
246 return 0;
247}
static int ret
Final status code.
Definition: gnunet-arm.c:94
static void open_dev_null(int target_fd, int flags)
Open '/dev/null' and make the result the given file descriptor.
static int status
The program status; 0 for success.
Definition: gnunet-nse.c:39
static struct GNUNET_PeerIdentity pid
Identity of the peer we transmit to / connect to.

References open_dev_null(), pid, ret, and status.

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ init_tun()

static int init_tun ( char *  dev)
static

Creates a tun-interface called dev;.

Parameters
devis assumed to point to a char[IFNAMSIZ] if *dev == '\0', uses the name supplied by the kernel;
Returns
the fd to the tun or -1 on error

Definition at line 258 of file gnunet-helper-dns.c.

259{
260 struct ifreq ifr;
261 int fd;
262
263 if (NULL == dev)
264 {
265 errno = EINVAL;
266 return -1;
267 }
268
269 if (-1 == (fd = open ("/dev/net/tun", O_RDWR)))
270 {
271 fprintf (stderr, "Error opening `%s': %s\n", "/dev/net/tun",
272 strerror (errno));
273 return -1;
274 }
275
276 if (fd >= FD_SETSIZE)
277 {
278 fprintf (stderr, "File descriptor to large: %d", fd);
279 (void) close (fd);
280 return -1;
281 }
282
283 memset (&ifr, 0, sizeof(ifr));
284 ifr.ifr_flags = IFF_TUN;
285
286 if ('\0' != *dev)
287 strncpy (ifr.ifr_name, dev, IFNAMSIZ);
288
289 if (-1 == ioctl (fd, TUNSETIFF, (void *) &ifr))
290 {
291 fprintf (stderr, "Error with ioctl on `%s': %s\n", "/dev/net/tun",
292 strerror (errno));
293 (void) close (fd);
294 return -1;
295 }
296 strcpy (dev, ifr.ifr_name);
297 return fd;
298}

Referenced by main().

Here is the caller graph for this function:

◆ set_address6()

static void set_address6 ( const char *  dev,
const char *  address,
unsigned long  prefix_len 
)
static

Sets the IPv6-Address given in address on the interface dev.

Parameters
devthe interface to configure
addressthe IPv6-Address
prefix_lenthe length of the network-prefix

Definition at line 309 of file gnunet-helper-dns.c.

310{
311 struct ifreq ifr;
312 struct in6_ifreq ifr6;
313 struct sockaddr_in6 sa6;
314 int fd;
315
316 /*
317 * parse the new address
318 */
319 memset (&sa6, 0, sizeof(struct sockaddr_in6));
320 sa6.sin6_family = AF_INET6;
321 if (1 != inet_pton (AF_INET6, address, sa6.sin6_addr.s6_addr))
322 {
323 fprintf (stderr,
324 "Failed to parse IPv6 address `%s': %s\n",
325 address,
326 strerror (errno));
327 exit (1);
328 }
329
330 if (-1 == (fd = socket (PF_INET6, SOCK_DGRAM, 0)))
331 {
332 fprintf (stderr,
333 "Error creating IPv6 socket: %s (ignored)\n",
334 strerror (errno));
335 /* ignore error, maybe only IPv4 works on this system! */
336 return;
337 }
338
339 memset (&ifr, 0, sizeof(struct ifreq));
340 /*
341 * Get the index of the if
342 */
343 strncpy (ifr.ifr_name, dev, IFNAMSIZ);
344 if (-1 == ioctl (fd, SIOGIFINDEX, &ifr))
345 {
346 fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno));
347 (void) close (fd);
348 exit (1);
349 }
350
351 memset (&ifr6, 0, sizeof(struct in6_ifreq));
352 ifr6.ifr6_addr = sa6.sin6_addr;
353 ifr6.ifr6_ifindex = ifr.ifr_ifindex;
354 ifr6.ifr6_prefixlen = prefix_len;
355
356 /*
357 * Set the address
358 */
359 if (-1 == ioctl (fd, SIOCSIFADDR, &ifr6))
360 {
361 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
362 strerror (errno));
363 (void) close (fd);
364 exit (1);
365 }
366
367 /*
368 * Get the flags
369 */
370 if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr))
371 {
372 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
373 strerror (errno));
374 (void) close (fd);
375 exit (1);
376 }
377
378 /*
379 * Add the UP and RUNNING flags
380 */
381 ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
382 if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr))
383 {
384 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
385 strerror (errno));
386 (void) close (fd);
387 exit (1);
388 }
389
390 if (0 != close (fd))
391 {
392 fprintf (stderr, "close failed: %s\n", strerror (errno));
393 exit (1);
394 }
395}
static char * address
GNS address for this phone.
This is in linux/include/net/ipv6.h, but not always exported...

References address, in6_ifreq::ifr6_addr, in6_ifreq::ifr6_ifindex, and in6_ifreq::ifr6_prefixlen.

Referenced by main().

Here is the caller graph for this function:

◆ set_address4()

static void set_address4 ( const char *  dev,
const char *  address,
const char *  mask 
)
static

Sets the IPv4-Address given in address on the interface dev.

Parameters
devthe interface to configure
addressthe IPv4-Address
maskthe netmask

Definition at line 406 of file gnunet-helper-dns.c.

407{
408 int fd;
409 struct sockaddr_in *addr;
410 struct ifreq ifr;
411
412 memset (&ifr, 0, sizeof(struct ifreq));
413 addr = (struct sockaddr_in *) &(ifr.ifr_addr);
414 addr->sin_family = AF_INET;
415
416 /*
417 * Parse the address
418 */
419 if (1 != inet_pton (AF_INET, address, &addr->sin_addr.s_addr))
420 {
421 fprintf (stderr,
422 "Failed to parse IPv4 address `%s': %s\n",
423 address,
424 strerror (errno));
425 exit (1);
426 }
427
428 if (-1 == (fd = socket (PF_INET, SOCK_DGRAM, 0)))
429 {
430 fprintf (stderr,
431 "Error creating IPv4 socket: %s\n",
432 strerror (errno));
433 exit (1);
434 }
435
436 strncpy (ifr.ifr_name, dev, IFNAMSIZ);
437
438 /*
439 * Set the address
440 */
441 if (-1 == ioctl (fd, SIOCSIFADDR, &ifr))
442 {
443 fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno));
444 (void) close (fd);
445 exit (1);
446 }
447
448 /*
449 * Parse the netmask
450 */
451 addr = (struct sockaddr_in *) &(ifr.ifr_netmask);
452 if (1 != inet_pton (AF_INET, mask, &addr->sin_addr.s_addr))
453 {
454 fprintf (stderr, "Failed to parse address `%s': %s\n", mask,
455 strerror (errno));
456 (void) close (fd);
457 exit (1);
458 }
459
460 /*
461 * Set the netmask
462 */
463 if (-1 == ioctl (fd, SIOCSIFNETMASK, &ifr))
464 {
465 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
466 strerror (errno));
467 (void) close (fd);
468 exit (1);
469 }
470
471 /*
472 * Get the flags
473 */
474 if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr))
475 {
476 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
477 strerror (errno));
478 (void) close (fd);
479 exit (1);
480 }
481
482 /*
483 * Add the UP and RUNNING flags
484 */
485 ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
486 if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr))
487 {
488 fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
489 strerror (errno));
490 (void) close (fd);
491 exit (1);
492 }
493
494 if (0 != close (fd))
495 {
496 fprintf (stderr, "close failed: %s\n", strerror (errno));
497 (void) close (fd);
498 exit (1);
499 }
500}

References address.

Referenced by main().

Here is the caller graph for this function:

◆ run()

static void run ( int  fd_tun)
static

Start forwarding to and from the tunnel.

This function runs with "reduced" privileges (saved UID is still 0, but effective UID is the real user ID).

Parameters
fd_tuntunnel FD

Definition at line 511 of file gnunet-helper-dns.c.

512{
513 /*
514 * The buffer filled by reading from fd_tun
515 */
516 unsigned char buftun[MAX_SIZE];
517 ssize_t buftun_size = 0;
518 unsigned char *buftun_read = NULL;
519
520 /*
521 * The buffer filled by reading from stdin
522 */
523 unsigned char bufin[MAX_SIZE];
524 ssize_t bufin_size = 0;
525 size_t bufin_rpos = 0;
526 unsigned char *bufin_read = NULL;
527 fd_set fds_w;
528 fd_set fds_r;
529 int max;
530
531 while (1)
532 {
533 FD_ZERO (&fds_w);
534 FD_ZERO (&fds_r);
535
536 /*
537 * We are supposed to read and the buffer is empty
538 * -> select on read from tun
539 */
540 if (0 == buftun_size)
541 FD_SET (fd_tun, &fds_r);
542
543 /*
544 * We are supposed to read and the buffer is not empty
545 * -> select on write to stdout
546 */
547 if (0 < buftun_size)
548 FD_SET (1, &fds_w);
549
550 /*
551 * We are supposed to write and the buffer is empty
552 * -> select on read from stdin
553 */
554 if (NULL == bufin_read)
555 FD_SET (0, &fds_r);
556
557 /*
558 * We are supposed to write and the buffer is not empty
559 * -> select on write to tun
560 */
561 if (NULL != bufin_read)
562 FD_SET (fd_tun, &fds_w);
563
564 FD_SET (cpipe[0], &fds_r);
565 max = (fd_tun > cpipe[0]) ? fd_tun : cpipe[0];
566
567 int r = select (max + 1, &fds_r, &fds_w, NULL, NULL);
568
569 if (-1 == r)
570 {
571 if (EINTR == errno)
572 continue;
573 fprintf (stderr, "select failed: %s\n", strerror (errno));
574 return;
575 }
576
577 if (r > 0)
578 {
579 if (FD_ISSET (cpipe[0], &fds_r))
580 return; /* aborted by signal */
581
582 if (FD_ISSET (fd_tun, &fds_r))
583 {
584 buftun_size =
585 read (fd_tun, buftun + sizeof(struct GNUNET_MessageHeader),
586 MAX_SIZE - sizeof(struct GNUNET_MessageHeader));
587 if (-1 == buftun_size)
588 {
589 if ((errno == EINTR) ||
590 (errno == EAGAIN))
591 {
592 buftun_size = 0;
593 continue;
594 }
595 fprintf (stderr, "read-error: %s\n", strerror (errno));
596 return;
597 }
598 if (0 == buftun_size)
599 {
600 fprintf (stderr, "EOF on tun\n");
601 return;
602 }
603 buftun_read = buftun;
604 {
605 struct GNUNET_MessageHeader *hdr =
606 (struct GNUNET_MessageHeader *) buftun;
607 buftun_size += sizeof(struct GNUNET_MessageHeader);
609 hdr->size = htons (buftun_size);
610 }
611 }
612 else if (FD_ISSET (1, &fds_w))
613 {
614 ssize_t written = write (1, buftun_read, buftun_size);
615
616 if (-1 == written)
617 {
618 if ((errno == EINTR) ||
619 (errno == EAGAIN))
620 continue;
621 fprintf (stderr, "write-error to stdout: %s\n", strerror (errno));
622 return;
623 }
624 if (0 == written)
625 {
626 fprintf (stderr, "write returned 0\n");
627 return;
628 }
629 buftun_size -= written;
630 buftun_read += written;
631 }
632
633 if (FD_ISSET (0, &fds_r))
634 {
635 bufin_size = read (0, bufin + bufin_rpos, MAX_SIZE - bufin_rpos);
636 if (-1 == bufin_size)
637 {
638 bufin_read = NULL;
639 if ((errno == EINTR) ||
640 (errno == EAGAIN))
641 continue;
642 fprintf (stderr, "read-error: %s\n", strerror (errno));
643 return;
644 }
645 if (0 == bufin_size)
646 {
647 bufin_read = NULL;
648 fprintf (stderr, "EOF on stdin\n");
649 return;
650 }
651 {
652 struct GNUNET_MessageHeader *hdr;
653
654PROCESS_BUFFER:
655 bufin_rpos += bufin_size;
656 if (bufin_rpos < sizeof(struct GNUNET_MessageHeader))
657 continue;
658 hdr = (struct GNUNET_MessageHeader *) bufin;
659 if (ntohs (hdr->type) != GNUNET_MESSAGE_TYPE_DNS_HELPER)
660 {
661 fprintf (stderr, "protocol violation!\n");
662 return;
663 }
664 if (ntohs (hdr->size) > bufin_rpos)
665 continue;
666 bufin_read = bufin + sizeof(struct GNUNET_MessageHeader);
667 bufin_size = ntohs (hdr->size) - sizeof(struct GNUNET_MessageHeader);
668 bufin_rpos -= bufin_size + sizeof(struct GNUNET_MessageHeader);
669 }
670 }
671 else if (FD_ISSET (fd_tun, &fds_w))
672 {
673 ssize_t written = write (fd_tun, bufin_read, bufin_size);
674
675 if (-1 == written)
676 {
677 if ((errno == EINTR) ||
678 (errno == EAGAIN))
679 continue;
680 fprintf (stderr, "write-error to tun: %s\n", strerror (errno));
681 return;
682 }
683 if (0 == written)
684 {
685 fprintf (stderr, "write returned 0\n");
686 return;
687 }
688 {
689 bufin_size -= written;
690 bufin_read += written;
691 if (0 == bufin_size)
692 {
693 memmove (bufin, bufin_read, bufin_rpos);
694 bufin_read = NULL; /* start reading again */
695 bufin_size = 0;
696 goto PROCESS_BUFFER;
697 }
698 }
699 }
700 }
701 }
702}
#define MAX_SIZE
Need 'struct GNUNET_MessageHeader'.
#define GNUNET_MESSAGE_TYPE_DNS_HELPER
Type of messages between the gnunet-helper-dns and the service.
#define max(x, y)
Header for all communications.
uint16_t type
The type of the message (GNUNET_MESSAGE_TYPE_XXXX), in big-endian format.
uint16_t size
The length of the struct (in bytes, including the length field itself), in big-endian format.

References cpipe, GNUNET_MESSAGE_TYPE_DNS_HELPER, max, MAX_SIZE, GNUNET_MessageHeader::size, and GNUNET_MessageHeader::type.

Referenced by main().

Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char *const *  argv 
)

Main function of "gnunet-helper-dns", which opens a VPN tunnel interface, redirects all outgoing DNS traffic (except from the specified port) to that interface and then passes traffic from and to the interface via stdin/stdout.

Once stdin/stdout close or have other errors, the tunnel is closed and the DNS traffic redirection is stopped.

Parameters
argcnumber of arguments
argv0: binary name (should be "gnunet-helper-vpn") 1: tunnel interface name (typically "gnunet-dns") 2: IPv6 address for the tunnel ("FE80::1") 3: IPv6 netmask length in bits ("64") 4: IPv4 address for the tunnel ("1.2.3.4") 5: IPv4 netmask ("255.255.0.0") 6: skip sysctl, routing and iptables setup ("0")
Returns
0 on success, otherwise code indicating type of error: 1 wrong number of arguments 2 invalid arguments (i.e. port number / prefix length wrong) 3 iptables not executable 4 ip not executable 5 failed to initialize tunnel interface 6 failed to initialize control pipe 8 failed to change routing table, cleanup successful 9-23 failed to change routing table and failed to undo some changes to routing table 24 failed to drop privs 25-39 failed to drop privs and then failed to undo some changes to routing table 40 failed to regain privs 41-55 failed to regain prisv and then failed to undo some changes to routing table 254 insufficient privileges 255 failed to handle kill signal properly

Definition at line 738 of file gnunet-helper-dns.c.

739{
740 int r;
741 char dev[IFNAMSIZ];
742 char mygid[32];
743 int fd_tun;
744 uid_t uid;
745 int nortsetup = 0;
746
747 if (7 != argc)
748 {
749 fprintf (stderr, "Fatal: must supply 6 arguments!\n");
750 return 1;
751 }
752
753 /* assert privs so we can modify the firewall rules! */
754 uid = getuid ();
755#ifdef HAVE_SETRESUID
756 if (0 != setresuid (uid, 0, 0))
757 {
758 fprintf (stderr, "Failed to setresuid to root: %s\n", strerror (errno));
759 return 254;
760 }
761#else
762 if (0 != seteuid (0))
763 {
764 fprintf (stderr, "Failed to seteuid back to root: %s\n", strerror (errno));
765 return 254;
766 }
767#endif
768 if (0 == strncmp (argv[6], "1", 2))
769 nortsetup = 1;
770
771 if (0 == nortsetup)
772 {
773 /* verify that the binaries we care about are executable */
774#ifdef IPTABLES
775 if (0 == access (IPTABLES, X_OK))
776 sbin_iptables = IPTABLES;
777 else
778#endif
779 if (0 == access ("/sbin/iptables", X_OK))
780 sbin_iptables = "/sbin/iptables";
781 else if (0 == access ("/usr/sbin/iptables", X_OK))
782 sbin_iptables = "/usr/sbin/iptables";
783 else
784 {
785 fprintf (stderr,
786 "Fatal: executable iptables not found in approved directories: %s\n",
787 strerror (errno));
788 return 3;
789 }
790#ifdef IP6TABLES
791 if (0 == access (IP6TABLES, X_OK))
792 sbin_ip6tables = IP6TABLES;
793 else
794#endif
795 if (0 == access ("/sbin/ip6tables", X_OK))
796 sbin_ip6tables = "/sbin/ip6tables";
797 else if (0 == access ("/usr/sbin/ip6tables", X_OK))
798 sbin_ip6tables = "/usr/sbin/ip6tables";
799 else
800 {
801 fprintf (stderr,
802 "Fatal: executable ip6tables not found in approved directories: %s\n",
803 strerror (errno));
804 return 3;
805 }
806#ifdef PATH_TO_IP
807 if (0 == access (PATH_TO_IP, X_OK))
808 sbin_ip = PATH_TO_IP;
809 else
810#endif
811 if (0 == access ("/sbin/ip", X_OK))
812 sbin_ip = "/sbin/ip";
813 else if (0 == access ("/usr/sbin/ip", X_OK))
814 sbin_ip = "/usr/sbin/ip";
815 else if (0 == access ("/bin/ip", X_OK)) /* gentoo has it there */
816 sbin_ip = "/bin/ip";
817 else
818 {
819 fprintf (stderr,
820 "Fatal: executable ip not found in approved directories: %s\n",
821 strerror (errno));
822 return 4;
823 }
824#ifdef SYSCTL
825 if (0 == access (SYSCTL, X_OK))
826 sbin_sysctl = SYSCTL;
827 else
828#endif
829 if (0 == access ("/sbin/sysctl", X_OK))
830 sbin_sysctl = "/sbin/sysctl";
831 else if (0 == access ("/usr/sbin/sysctl", X_OK))
832 sbin_sysctl = "/usr/sbin/sysctl";
833 else
834 {
835 fprintf (stderr,
836 "Fatal: executable sysctl not found in approved directories: %s\n",
837 strerror (errno));
838 return 5;
839 }
840 }
841
842 /* setup 'mygid' string */
843 snprintf (mygid, sizeof(mygid), "%d", (int) getegid ());
844
845 /* do not die on SIGPIPE */
846 if (SIG_ERR == signal (SIGPIPE, SIG_IGN))
847 {
848 fprintf (stderr, "Failed to protect against SIGPIPE: %s\n",
849 strerror (errno));
850 return 7;
851 }
852
853 /* setup pipe to shutdown nicely on SIGINT */
854 if (0 != pipe (cpipe))
855 {
856 fprintf (stderr,
857 "Fatal: could not setup control pipe: %s\n",
858 strerror (errno));
859 return 6;
860 }
861 if (cpipe[0] >= FD_SETSIZE)
862 {
863 fprintf (stderr, "Pipe file descriptor to large: %d", cpipe[0]);
864 (void) close (cpipe[0]);
865 (void) close (cpipe[1]);
866 return 6;
867 }
868 {
869 /* make pipe non-blocking, as we theoretically could otherwise block
870 in the signal handler */
871 int flags = fcntl (cpipe[1], F_GETFL);
872 if (-1 == flags)
873 {
874 fprintf (stderr, "Failed to read flags for pipe: %s", strerror (errno));
875 (void) close (cpipe[0]);
876 (void) close (cpipe[1]);
877 return 6;
878 }
879 flags |= O_NONBLOCK;
880 if (0 != fcntl (cpipe[1], F_SETFL, flags))
881 {
882 fprintf (stderr, "Failed to make pipe non-blocking: %s", strerror (
883 errno));
884 (void) close (cpipe[0]);
885 (void) close (cpipe[1]);
886 return 6;
887 }
888 }
889 if ((SIG_ERR == signal (SIGTERM, &signal_handler)) ||
890#if (SIGTERM != GNUNET_TERM_SIG)
891 (SIG_ERR == signal (GNUNET_TERM_SIG, &signal_handler)) ||
892#endif
893 (SIG_ERR == signal (SIGINT, &signal_handler)) ||
894 (SIG_ERR == signal (SIGHUP, &signal_handler)))
895 {
896 fprintf (stderr,
897 "Fatal: could not initialize signal handler: %s\n",
898 strerror (errno));
899 (void) close (cpipe[0]);
900 (void) close (cpipe[1]);
901 return 7;
902 }
903
904
905 /* get interface name */
906 strncpy (dev, argv[1], IFNAMSIZ);
907 dev[IFNAMSIZ - 1] = '\0';
908
909 /* Disable rp filtering */
910 if (0 == nortsetup)
911 {
912 char *const sysctl_args[] = { "sysctl", "-w",
913 "net.ipv4.conf.all.rp_filter=0", NULL };
914 char *const sysctl_args2[] = { "sysctl", "-w",
915 "net.ipv4.conf.default.rp_filter=0", NULL };
916 if ((0 != fork_and_exec (sbin_sysctl, sysctl_args)) ||
917 (0 != fork_and_exec (sbin_sysctl, sysctl_args2)))
918 {
919 fprintf (stderr,
920 "Failed to disable rp filtering.\n");
921 return 5;
922 }
923 }
924
925
926 /* now open virtual interface (first part that requires root) */
927 if (-1 == (fd_tun = init_tun (dev)))
928 {
929 fprintf (stderr, "Fatal: could not initialize tun-interface\n");
930 (void) signal (SIGTERM, SIG_IGN);
931#if (SIGTERM != GNUNET_TERM_SIG)
932 (void) signal (GNUNET_TERM_SIG, SIG_IGN);
933#endif
934 (void) signal (SIGINT, SIG_IGN);
935 (void) signal (SIGHUP, SIG_IGN);
936 (void) close (cpipe[0]);
937 (void) close (cpipe[1]);
938 return 5;
939 }
940
941 /* now set interface addresses */
942 {
943 const char *address = argv[2];
944 long prefix_len = atol (argv[3]);
945
946 if ((prefix_len < 1) || (prefix_len > 127))
947 {
948 fprintf (stderr, "Fatal: prefix_len out of range\n");
949 (void) signal (SIGTERM, SIG_IGN);
950#if (SIGTERM != GNUNET_TERM_SIG)
951 (void) signal (GNUNET_TERM_SIG, SIG_IGN);
952#endif
953 (void) signal (SIGINT, SIG_IGN);
954 (void) signal (SIGHUP, SIG_IGN);
955 (void) close (cpipe[0]);
956 (void) close (cpipe[1]);
957 return 2;
958 }
959 set_address6 (dev, address, prefix_len);
960 }
961
962 {
963 const char *address = argv[4];
964 const char *mask = argv[5];
965
966 set_address4 (dev, address, mask);
967 }
968
969
970 /* update routing tables -- next part why we need SUID! */
971 /* Forward everything from our EGID (which should only be held
972 by the 'gnunet-service-dns') and with destination
973 to port 53 on UDP, without hijacking */
974 if (0 == nortsetup)
975 {
976 r = 8; /* failed to fully setup routing table */
977 {
978 char *const mangle_args[] = {
979 "iptables", "-m", "owner", "-t", "mangle", "-I", "OUTPUT", "1", "-p",
980 "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j",
981 "ACCEPT", NULL
982 };
983 if (0 != fork_and_exec (sbin_iptables, mangle_args))
984 goto cleanup_rest;
985 }
986 {
987 char *const mangle_args[] = {
988 "ip6tables", "-m", "owner", "-t", "mangle", "-I", "OUTPUT", "1", "-p",
989 "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j",
990 "ACCEPT", NULL
991 };
992 if (0 != fork_and_exec (sbin_ip6tables, mangle_args))
993 goto cleanup_mangle_1b;
994 }
995 /* Mark all of the other DNS traffic using our mark DNS_MARK,
996 unless it is on a link-local IPv6 address, which we cannot support. */
997 {
998 char *const mark_args[] = {
999 "iptables", "-t", "mangle", "-I", "OUTPUT", "2", "-p",
1000 "udp", "--dport", DNS_PORT,
1001 "-j", "MARK", "--set-mark", DNS_MARK,
1002 NULL
1003 };
1004 if (0 != fork_and_exec (sbin_iptables, mark_args))
1005 goto cleanup_mangle_1;
1006 }
1007 {
1008 char *const mark_args[] = {
1009 "ip6tables", "-t", "mangle", "-I", "OUTPUT", "2", "-p",
1010 "udp", "--dport", DNS_PORT,
1011 "!", "-s", "fe80::/10", /* this line excludes link-local traffic */
1012 "-j", "MARK", "--set-mark", DNS_MARK,
1013 NULL
1014 };
1015 if (0 != fork_and_exec (sbin_ip6tables, mark_args))
1016 goto cleanup_mark_2b;
1017 }
1018 /* Forward all marked DNS traffic to our DNS_TABLE */
1019 {
1020 char *const forward_args[] = {
1021 "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
1022 };
1023 if (0 != fork_and_exec (sbin_ip, forward_args))
1024 goto cleanup_mark_2;
1025 }
1026 {
1027 char *const forward_args[] = {
1028 "ip", "-6", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
1029 };
1030 if (0 != fork_and_exec (sbin_ip, forward_args))
1031 goto cleanup_forward_3b;
1032 }
1033 /* Finally, add rule in our forwarding table to pass to our virtual interface */
1034 {
1035 char *const route_args[] = {
1036 "ip", "route", "add", "default", "dev", dev,
1037 "table", DNS_TABLE, NULL
1038 };
1039 if (0 != fork_and_exec (sbin_ip, route_args))
1040 goto cleanup_forward_3;
1041 }
1042 {
1043 char *const route_args[] = {
1044 "ip", "-6", "route", "add", "default", "dev", dev,
1045 "table", DNS_TABLE, NULL
1046 };
1047 if (0 != fork_and_exec (sbin_ip, route_args))
1048 goto cleanup_route_4b;
1049 }
1050 }
1051
1052 /* drop privs *except* for the saved UID; this is not perfect, but better
1053 than doing nothing */
1054#ifdef HAVE_SETRESUID
1055 if (0 != setresuid (uid, uid, 0))
1056 {
1057 fprintf (stderr, "Failed to setresuid: %s\n", strerror (errno));
1058 r = 24;
1059 goto cleanup_route_4;
1060 }
1061#else
1062 /* Note: no 'setuid' here as we must keep our saved UID as root */
1063 if (0 != seteuid (uid))
1064 {
1065 fprintf (stderr, "Failed to seteuid: %s\n", strerror (errno));
1066 r = 24;
1067 goto cleanup_route_4;
1068 }
1069#endif
1070
1071 r = 0; /* did fully setup routing table (if nothing else happens, we were successful!) */
1072
1073 /* now forward until we hit a problem */
1074 run (fd_tun);
1075
1076 /* now need to regain privs so we can remove the firewall rules we added! */
1077#ifdef HAVE_SETRESUID
1078 if (0 != setresuid (uid, 0, 0))
1079 {
1080 fprintf (stderr, "Failed to setresuid back to root: %s\n", strerror (
1081 errno));
1082 r = 40;
1083 goto cleanup_route_4;
1084 }
1085#else
1086 if (0 != seteuid (0))
1087 {
1088 fprintf (stderr, "Failed to seteuid back to root: %s\n", strerror (errno));
1089 r = 40;
1090 goto cleanup_route_4;
1091 }
1092#endif
1093
1094 /* update routing tables again -- this is why we could not fully drop privs */
1095 /* now undo updating of routing tables; normal exit or clean-up-on-error case */
1096cleanup_route_4:
1097 if (0 == nortsetup)
1098 {
1099 char *const route_clean_args[] = {
1100 "ip", "-6", "route", "del", "default", "dev", dev,
1101 "table", DNS_TABLE, NULL
1102 };
1103 if (0 != fork_and_exec (sbin_ip, route_clean_args))
1104 r += 1;
1105 }
1106cleanup_route_4b:
1107 if (0 == nortsetup)
1108 {
1109 char *const route_clean_args[] = {
1110 "ip", "route", "del", "default", "dev", dev,
1111 "table", DNS_TABLE, NULL
1112 };
1113 if (0 != fork_and_exec (sbin_ip, route_clean_args))
1114 r += 1;
1115 }
1116cleanup_forward_3:
1117 if (0 == nortsetup)
1118 {
1119 char *const forward_clean_args[] = {
1120 "ip", "-6", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
1121 };
1122 if (0 != fork_and_exec (sbin_ip, forward_clean_args))
1123 r += 2;
1124 }
1125cleanup_forward_3b:
1126 if (0 == nortsetup)
1127 {
1128 char *const forward_clean_args[] = {
1129 "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
1130 };
1131 if (0 != fork_and_exec (sbin_ip, forward_clean_args))
1132 r += 2;
1133 }
1134cleanup_mark_2:
1135 if (0 == nortsetup)
1136 {
1137 char *const mark_clean_args[] = {
1138 "ip6tables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
1139 "--dport", DNS_PORT,
1140 "!", "-s", "fe80::/10", /* this line excludes link-local traffic */
1141 "-j", "MARK", "--set-mark", DNS_MARK, NULL
1142 };
1143 if (0 != fork_and_exec (sbin_ip6tables, mark_clean_args))
1144 r += 4;
1145 }
1146cleanup_mark_2b:
1147 if (0 == nortsetup)
1148 {
1149 char *const mark_clean_args[] = {
1150 "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
1151 "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL
1152 };
1153 if (0 != fork_and_exec (sbin_iptables, mark_clean_args))
1154 r += 4;
1155 }
1156cleanup_mangle_1:
1157 if (0 == nortsetup)
1158 {
1159 char *const mangle_clean_args[] = {
1160 "ip6tables", "-m", "owner", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
1161 "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT",
1162 NULL
1163 };
1164 if (0 != fork_and_exec (sbin_ip6tables, mangle_clean_args))
1165 r += 8;
1166 }
1167cleanup_mangle_1b:
1168 if (0 == nortsetup)
1169 {
1170 char *const mangle_clean_args[] = {
1171 "iptables", "-m", "owner", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
1172 "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT",
1173 NULL
1174 };
1175 if (0 != fork_and_exec (sbin_iptables, mangle_clean_args))
1176 r += 8;
1177 }
1178
1179cleanup_rest:
1180 /* close virtual interface */
1181 (void) close (fd_tun);
1182 /* remove signal handler so we can close the pipes */
1183 (void) signal (SIGTERM, SIG_IGN);
1184#if (SIGTERM != GNUNET_TERM_SIG)
1185 (void) signal (GNUNET_TERM_SIG, SIG_IGN);
1186#endif
1187 (void) signal (SIGINT, SIG_IGN);
1188 (void) signal (SIGHUP, SIG_IGN);
1189 (void) close (cpipe[0]);
1190 (void) close (cpipe[1]);
1191 return r;
1192}
#define DNS_TABLE
Table we use for our DNS rules.
static const char * sbin_sysctl
Name and full path of sysctl binary.
static const char * sbin_ip
Name and full path of IPTABLES binary.
static void run(int fd_tun)
Start forwarding to and from the tunnel.
static int fork_and_exec(const char *file, char *const cmd[])
Run the given command and wait for it to complete.
#define DNS_MARK
Marker we set for our hijacked DNS traffic.
static const char * sbin_iptables
Name and full path of IPTABLES binary.
static void signal_handler(int signal)
Signal handler called to initiate "nice" shutdown.
static int init_tun(char *dev)
Creates a tun-interface called dev;.
static void set_address4(const char *dev, const char *address, const char *mask)
Sets the IPv4-Address given in address on the interface dev.
static const char * sbin_ip6tables
Name and full path of IPTABLES binary.
#define DNS_PORT
Port for DNS traffic.
static void set_address6(const char *dev, const char *address, unsigned long prefix_len)
Sets the IPv6-Address given in address on the interface dev.
#define GNUNET_TERM_SIG
The termination signal.
Definition: platform.h:234

References address, cpipe, DNS_MARK, DNS_PORT, DNS_TABLE, fork_and_exec(), GNUNET_TERM_SIG, init_tun(), run(), sbin_ip, sbin_ip6tables, sbin_iptables, sbin_sysctl, set_address4(), set_address6(), and signal_handler().

Here is the call graph for this function:

Variable Documentation

◆ sbin_iptables

const char* sbin_iptables
static

Name and full path of IPTABLES binary.

Definition at line 103 of file gnunet-helper-dns.c.

Referenced by main().

◆ sbin_ip6tables

const char* sbin_ip6tables
static

Name and full path of IPTABLES binary.

Definition at line 108 of file gnunet-helper-dns.c.

Referenced by main().

◆ sbin_sysctl

const char* sbin_sysctl
static

Name and full path of sysctl binary.

Definition at line 113 of file gnunet-helper-dns.c.

Referenced by main().

◆ sbin_ip

const char* sbin_ip
static

Name and full path of IPTABLES binary.

Definition at line 118 of file gnunet-helper-dns.c.

Referenced by main(), and try_ip().

◆ cpipe

int cpipe[2]
static

Control pipe for shutdown via signal.

[0] is the read end, [1] is the write end.

Definition at line 147 of file gnunet-helper-dns.c.

Referenced by main(), run(), and signal_handler().