GNUnet  0.20.0
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.

◆ 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
Return value of the commandline.
Definition: gnunet-abd.c:81
static void open_dev_null(int target_fd, int flags)
Open '/dev/null' 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.

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

Here is the call 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 }

◆ 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.

◆ 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.

◆ 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);
608  hdr->type = htons (GNUNET_MESSAGE_TYPE_DNS_HELPER);
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 
654 PROCESS_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.

◆ 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 */
1096 cleanup_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  }
1106 cleanup_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  }
1116 cleanup_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  }
1125 cleanup_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  }
1134 cleanup_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  }
1146 cleanup_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  }
1156 cleanup_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  }
1167 cleanup_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 
1179 cleanup_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

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.

◆ sbin_ip6tables

const char* sbin_ip6tables
static

Name and full path of IPTABLES binary.

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

◆ sbin_sysctl

const char* sbin_sysctl
static

Name and full path of sysctl binary.

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

◆ 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 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 run(), and signal_handler().