May 9, 2016

Example code of IPv4 and IPv6 using FREEBIND and IP_TRANSPARENT socket options to send packets using a non-local IP address



http://lists.openwall.net/netdev/2011/11/02/4

Use  IP_PKTINFO to  set the source IP address if you do not bind it to a particular address.

http://man7.org/linux/man-pages/man7/ip.7.html

Date: Tue, 1 Nov 2011 17:57:07 -0700
From: Maciej Żenczykowski <zenczykowski@...il.com>
To: Linux NetDev <netdev@...r.kernel.org>
Subject: On IP_FREEBIND and IPv6...

Short summary:
  IPV6 + IP_FREEBIND doesn't work the way IPV4 + IP_FREEBIND does.
  The native IPv6 bind path ignores 'freebind', but honours 'transparent'.
  The native and dual-stack IPv4 bind paths honour both.

Does anyone know if this was a (security?) feature?  Or is this just a bug?

I'll follow this up with a patch to support freebind for v6 bind (and
another one for v6 udp sendmsg).
Unless I hear some compelling story about why stuff is the way it is.

---

Please find test program source later on.

It basically does:
   for test_mode in {native_ipv4, ipv4_on_ipv6_socket, native_ipv6} do:
       create a udp socket
       set IP_FREEBIND=1
       set IP_TRANSPARENT=1 (will fail if not root, ignore failure)
       bind socket to an IP address we don't own (one of: 1.2.3.4,
::FFFF:1.2.3.4, 2001:4860:DEAD:CAFE::6006:13) [fails without root for
native ipv6]
       send a packet to another IP address (one of: 5.6.7.8,
::FFFF:5.6.7.8, 2001:4860:DEAD:BEEF::6006:13)

Running it generates:

$ ./test
setsockopt(TRANSPARENT=1): Operation not permitted [requires root]
setsockopt(TRANSPARENT=1): Operation not permitted [requires root]
setsockopt(TRANSPARENT=1): Operation not permitted [requires root]
bind(): Cannot assign requested address [native ipv6 bind does not
honour IP_FREEBIND, does honour IP{,V6}_TRANSPARENT]
$ sudo ./test
<no errors, everything succeeds, including bind native ipv6>

While running tcpdump shows:
# tcpdump -s 1555 -n -nn -i eth0 port 11111 or port 22222

>From ./a [ie. with IP_FREEBIND=1, IP_TRANSPARENT=0]:

IP 1.2.3.4.11111 > 5.6.7.8.22222: UDP, length 6 [native IPv4]
IP 1.2.3.4.11111 > 5.6.7.8.22222: UDP, length 6 [dual stack IPv4 on IPv6 socket]
IP6 [machines_true_ipv6_address].51912 >
2001:4860:dead:beef::6006:13.22222: UDP, length 6 [native IPv6, wrong
source since bind failed]

>From sudo ./a [ie. with IP_FREEBIND=1, IP_TRANSPARENT=1]:

IP 1.2.3.4.11111 > 5.6.7.8.22222: UDP, length 6 [native IPv4]
IP 1.2.3.4.11111 > 5.6.7.8.22222: UDP, length 6 [dual stack IPv4 on IPv6 socket]
IP6 2001:4860:dead:cafe::6006:13.vce >
2001:4860:dead:beef::6006:13.22222: UDP, length 6 [native IPv6]

This seems to prove that IP_TRANSPARENT requires root - this is as
expected, while IP_FREEBIND does not require root - again as expected.
However, as apparent above, we are successfully spoofing outgoing
source address on IPv4 UDP (whether native IPv4 or dual-stack IPv4
doesn't matter),
but there doesn't seem to be a way to do this with native IPv6.

ie. the native IPv6 bind path ignores the "freebind" setting, but does
honour the "transparent", while the IPv4 code paths honour both.

- Maciej

---
#include <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#define NATIVE_IPv4 0
#define DUAL_STACK  1
#define NATIVE_IPv6 2

int main(int argc, char const * argv[], char const * envp[]) {
  struct sockaddr_in saddr4, daddr4;
  struct sockaddr_in6 saddr6, daddr6;
  int fd, rv, v, mode;

  for (mode = 0; mode <= 2; ++mode) {

    if (mode == NATIVE_IPv4) {
      fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
      if (fd < 0) perror("socket(IPv4 UDP)");
    } else {
      fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
      if (fd < 0) perror("socket(IPv6 UDP)");
    }

    v = 1;
    rv = setsockopt(fd, SOL_IP, IP_FREEBIND, &v, sizeof(v));
    if (rv < 0) perror("setsockopt(FREEBIND=1)");

    v = 1;
    rv = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &v, sizeof(v));
    if (rv < 0) perror("setsockopt(TRANSPARENT=1)");

    if (mode == NATIVE_IPv4) {
      memset(&saddr4, 0, sizeof(saddr4));
      memset(&daddr4, 0, sizeof(daddr4));
      saddr4.sin_family = AF_INET;
      daddr4.sin_family = AF_INET;
      saddr4.sin_port = htons(11111);
      daddr4.sin_port = htons(22222);
      inet_pton(AF_INET, "1.2.3.4", &saddr4.sin_addr.s_addr);
      inet_pton(AF_INET, "5.6.7.8", &daddr4.sin_addr.s_addr);

      rv = bind(fd, (struct sockaddr const *)&saddr4, sizeof(saddr4));
      if (rv < 0) perror("bind()");

      rv = sendto(fd, "Hello!", 6, 0, (struct sockaddr const
*)&daddr4, sizeof(daddr4));
      if (rv < 0) perror("write");
    } else {
      memset(&saddr6, 0, sizeof(saddr6));
      memset(&daddr6, 0, sizeof(daddr6));
      saddr6.sin6_family = AF_INET6;
      daddr6.sin6_family = AF_INET6;
      saddr6.sin6_port = htons(11111);
      daddr6.sin6_port = htons(22222);
      //saddr6.sin6_flowinfo = 0;
      //daddr6.sin6_flowinfo = 0;
      if (mode == DUAL_STACK) {
        inet_pton(AF_INET6, "::FFFF:1.2.3.4", &saddr6.sin6_addr);
        inet_pton(AF_INET6, "::FFFF:5.6.7.8", &daddr6.sin6_addr);
      } else {
        inet_pton(AF_INET6, "2001:4860:DEAD:CAFE::6006:0013",
&saddr6.sin6_addr);
        inet_pton(AF_INET6, "2001:4860:DEAD:BEEF::6006:0013",
&daddr6.sin6_addr);
      }
      //saddr6.sin6_scope_id = 0;
      //daddr6.sin6_scope_id = 0;

      rv = bind(fd, (struct sockaddr const *)&saddr6, sizeof(saddr6));
      if (rv < 0) perror("bind()");

      rv = sendto(fd, "Hello!", 6, 0, (struct sockaddr const
*)&daddr6, sizeof(daddr6));
      if (rv < 0) perror("write");
    }

    rv = close(fd);
    if (rv < 0) perror("close");
  }

  return 0;
}
--


BELOW is my adapted version:

#include <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
//#include <linux/in.h>
#include <arpa/inet.h>

#define NATIVE_IPv4 0
#define DUAL_STACK  1
#define NATIVE_IPv6 2

#if 1
#if !defined(IP_FREEBIND)
#define IP_FREEBIND 15
#endif /* !IP_FREEBIND */
#if !defined(IP_TRANSPARENT)
#define IP_TRANSPARENT 19
#endif /* !IP_TRANSPARENT */
#endif

#define IPV6_TRANSPARENT        75

int main(int argc, char const * argv[], char const * envp[]) {
        struct sockaddr_in saddr4, daddr4;
        struct sockaddr_in6 saddr6, daddr6;
        int fd, rv, v, mode;

        for (mode = 2; mode <= 2; ++mode) {

                if (mode == NATIVE_IPv4) {
                        fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
                        if (fd < 0) perror("socket(IPv4 UDP)");
                } else {
                        fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
                        if (fd < 0) perror("socket(IPv6 UDP)");
                }

                v = 1;
                rv = setsockopt(fd, SOL_IP, IP_FREEBIND, &v, sizeof(v));
                if (rv < 0) perror("setsockopt(FREEBIND=1)");

                if (mode == NATIVE_IPv4) {
                        v = 1;
                        rv = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &v, sizeof(v));
                        if (rv < 0) perror("setsockopt(TRANSPARENT=1)");
                }else{
                        v = 1;
                        rv = setsockopt(fd, SOL_IPV6, IPV6_TRANSPARENT, &v, sizeof(v));
                        if (rv < 0) perror("setsockopt ipv6 (TRANSPARENT=1)");
                }

                if (mode == NATIVE_IPv4) {
                        memset(&saddr4, 0, sizeof(saddr4));
                        memset(&daddr4, 0, sizeof(daddr4));
                        saddr4.sin_family = AF_INET;
                        daddr4.sin_family = AF_INET;
                        saddr4.sin_port = htons(11111);
                        daddr4.sin_port = htons(22222);
                        inet_pton(AF_INET, "1.2.3.4", &saddr4.sin_addr.s_addr);
                        inet_pton(AF_INET, "5.6.7.8", &daddr4.sin_addr.s_addr);

                        rv = bind(fd, (struct sockaddr const *)&saddr4, sizeof(saddr4));
                        if (rv < 0) perror("bind()");

                        rv = sendto(fd, "Hello!", 6, 0, (struct sockaddr const
                                                *)&daddr4, sizeof(daddr4));
                        if (rv < 0) perror("write");
                } else {
                        memset(&saddr6, 0, sizeof(saddr6));
                        memset(&daddr6, 0, sizeof(daddr6));
                        saddr6.sin6_family = AF_INET6;
                        daddr6.sin6_family = AF_INET6;
                        saddr6.sin6_port = htons(11111);
                        daddr6.sin6_port = htons(22222);
                        //saddr6.sin6_flowinfo = 0;
                        //daddr6.sin6_flowinfo = 0;
                        if (mode == DUAL_STACK) {
                                inet_pton(AF_INET6, "::FFFF:1.2.3.4", &saddr6.sin6_addr);
                                inet_pton(AF_INET6, "::FFFF:5.6.7.8", &daddr6.sin6_addr);
                        } else {
                                inet_pton(AF_INET6, "2001:4860:DEAD:CAFE::6006:0013",
                                                &saddr6.sin6_addr);
                                inet_pton(AF_INET6, "2001:4860:DEAD:BEEF::6006:0013",
                                                &daddr6.sin6_addr);
                        }
                        //saddr6.sin6_scope_id = 0;
                        //daddr6.sin6_scope_id = 0;

                        rv = bind(fd, (struct sockaddr const *)&saddr6, sizeof(saddr6));
                        if (rv < 0) perror("bind()");

                        rv = sendto(fd, "Hello!", 6, 0, (struct sockaddr const
                                                *)&daddr6, sizeof(daddr6));
                        if (rv < 0) perror("write");
                }

                rv = close(fd);
                if (rv < 0) perror("close");
        }

        return 0;

No comments:

Post a Comment