GNUnet  0.11.x
Data Structures | Macros | Functions | Variables
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 <linux/if_tun.h>
#include "gnunet_crypto_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):

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 84 of file gnunet-helper-dns.c.

Referenced by run().

◆ DNS_PORT

#define DNS_PORT   "53"

Port for DNS traffic.

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

Referenced by main().

◆ 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 129 of file gnunet-helper-dns.c.

Referenced by main().

◆ 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 138 of file gnunet-helper-dns.c.

Referenced by main().

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 155 of file gnunet-helper-dns.c.

References cpipe.

Referenced by main().

156 {
157  /* ignore return value, as the signal handler could theoretically
158  be called many times before the shutdown can actually happen */
159  (void) write (cpipe[1], "K", 1);
160 }
static int cpipe[2]
Control pipe for shutdown via signal.
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 171 of file gnunet-helper-dns.c.

Referenced by fork_and_exec().

173 {
174  int fd;
175 
176  fd = open ("/dev/null", flags);
177  if (-1 == fd)
178  abort ();
179  if (fd == target_fd)
180  return;
181  if (-1 == dup2 (fd, target_fd))
182  {
183  (void) close (fd);
184  abort ();
185  }
186  (void) close (fd);
187 }
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 198 of file gnunet-helper-dns.c.

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

Referenced by main().

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

Referenced by main().

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

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

Referenced by main().

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

Referenced by main().

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

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

Referenced by main().

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

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().

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

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