socket-dynamic: when sending from port zero, allocate a free port dynamically
authorMartin Willi <martin@revosec.ch>
Tue, 26 Mar 2013 16:23:38 +0000 (17:23 +0100)
committerMartin Willi <martin@revosec.ch>
Mon, 6 May 2013 13:28:26 +0000 (15:28 +0200)
src/libcharon/plugins/socket_dynamic/socket_dynamic_socket.c

index a5e9193..b7c7394 100644 (file)
@@ -326,13 +326,60 @@ METHOD(socket_t, receiver, status_t,
 }
 
 /**
+ * Get the port allocated dynamically using bind()
+ */
+static bool get_dynamic_port(int fd, int family, u_int16_t *port)
+{
+       union {
+               struct sockaddr_storage ss;
+               struct sockaddr s;
+               struct sockaddr_in sin;
+               struct sockaddr_in6 sin6;
+       } addr;
+       socklen_t addrlen;
+
+       addrlen = sizeof(addr);
+       if (getsockname(fd, &addr.s, &addrlen) != 0)
+       {
+               DBG1(DBG_NET, "unable to getsockname: %s", strerror(errno));
+               return FALSE;
+       }
+       switch (family)
+       {
+               case AF_INET:
+                       if (addrlen != sizeof(addr.sin) || addr.sin.sin_family != family)
+                       {
+                               break;
+                       }
+                       *port = ntohs(addr.sin.sin_port);
+                       return TRUE;
+               case AF_INET6:
+                       if (addrlen != sizeof(addr.sin6) || addr.sin6.sin6_family != family)
+                       {
+                               break;
+                       }
+                       *port = ntohs(addr.sin6.sin6_port);
+                       return TRUE;
+               default:
+                       return FALSE;
+       }
+       DBG1(DBG_NET, "received invalid getsockname() result");
+       return FALSE;
+}
+
+/**
  * open a socket to send and receive packets
  */
 static int open_socket(private_socket_dynamic_socket_t *this,
-                                          int family, u_int16_t port)
+                                          int family, u_int16_t *port)
 {
+       union {
+               struct sockaddr_storage ss;
+               struct sockaddr s;
+               struct sockaddr_in sin;
+               struct sockaddr_in6 sin6;
+       } addr;
        int on = TRUE;
-       struct sockaddr_storage addr;
        socklen_t addrlen;
        u_int sol, pktinfo = 0;
        int fd;
@@ -342,27 +389,21 @@ static int open_socket(private_socket_dynamic_socket_t *this,
        switch (family)
        {
                case AF_INET:
-               {
-                       struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
-                       sin->sin_family = AF_INET;
-                       sin->sin_addr.s_addr = INADDR_ANY;
-                       sin->sin_port = htons(port);
-                       addrlen = sizeof(struct sockaddr_in);
+                       addr.sin.sin_family = AF_INET;
+                       addr.sin.sin_addr.s_addr = INADDR_ANY;
+                       addr.sin.sin_port = htons(*port);
+                       addrlen = sizeof(addr.sin);
                        sol = SOL_IP;
                        pktinfo = IP_PKTINFO;
                        break;
-               }
                case AF_INET6:
-               {
-                       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
-                       sin6->sin6_family = AF_INET6;
-                       memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr));
-                       sin6->sin6_port = htons(port);
-                       addrlen = sizeof(struct sockaddr_in6);
+                       addr.sin6.sin6_family = AF_INET6;
+                       memset(&addr.sin6.sin6_addr, 0, sizeof(addr.sin6));
+                       addr.sin6.sin6_port = htons(*port);
+                       addrlen = sizeof(addr.sin6);
                        sol = SOL_IPV6;
                        pktinfo = IPV6_RECVPKTINFO;
                        break;
-               }
                default:
                        return 0;
        }
@@ -380,13 +421,17 @@ static int open_socket(private_socket_dynamic_socket_t *this,
                return 0;
        }
 
-       /* bind the socket */
-       if (bind(fd, (struct sockaddr *)&addr, addrlen) < 0)
+       if (bind(fd, &addr.s, addrlen) < 0)
        {
                DBG1(DBG_NET, "unable to bind socket: %s", strerror(errno));
                close(fd);
                return 0;
        }
+       if (*port == 0 && !get_dynamic_port(fd, family, port))
+       {
+               close(fd);
+               return 0;
+       }
 
        /* get additional packet info on receive */
        if (setsockopt(fd, sol, pktinfo, &on, sizeof(on)) < 0)
@@ -404,16 +449,41 @@ static int open_socket(private_socket_dynamic_socket_t *this,
 
        /* enable UDP decapsulation on each socket */
        if (!hydra->kernel_interface->enable_udp_decap(hydra->kernel_interface,
-                                                                                                  fd, family, port))
+                                                                                                  fd, family, *port))
        {
                DBG1(DBG_NET, "enabling UDP decapsulation for %s on port %d failed",
-                        family == AF_INET ? "IPv4" : "IPv6", port);
+                        family == AF_INET ? "IPv4" : "IPv6", *port);
        }
 
        return fd;
 }
 
 /**
+ * Get the first usable socket for an address family
+ */
+static dynsock_t *get_any_socket(private_socket_dynamic_socket_t *this,
+                                                                int family)
+{
+       dynsock_t *key, *value, *found = NULL;
+       enumerator_t *enumerator;
+
+       this->lock->read_lock(this->lock);
+       enumerator = this->sockets->create_enumerator(this->sockets);
+       while (enumerator->enumerate(enumerator, &key, &value))
+       {
+               if (value->family == family)
+               {
+                       found = value;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       this->lock->unlock(this->lock);
+
+       return found;
+}
+
+/**
  * Find/Create a socket to send from host
  */
 static dynsock_t *find_socket(private_socket_dynamic_socket_t *this,
@@ -433,7 +503,15 @@ static dynsock_t *find_socket(private_socket_dynamic_socket_t *this,
        {
                return skt;
        }
-       fd = open_socket(this, family, port);
+       if (!port)
+       {
+               skt = get_any_socket(this, family);
+               if (skt)
+               {
+                       return skt;
+               }
+       }
+       fd = open_socket(this, family, &port);
        if (!fd)
        {
                return NULL;
@@ -457,7 +535,7 @@ METHOD(socket_t, sender, status_t,
 {
        dynsock_t *skt;
        host_t *src, *dst;
-       int port, family;
+       int family;
        ssize_t len;
        chunk_t data;
        struct msghdr msg;
@@ -467,9 +545,7 @@ METHOD(socket_t, sender, status_t,
        src = packet->get_source(packet);
        dst = packet->get_destination(packet);
        family = src->get_family(src);
-       port = src->get_port(src);
-       port = port ?: CHARON_UDP_PORT;
-       skt = find_socket(this, family, port);
+       skt = find_socket(this, family, src->get_port(src));
        if (!skt)
        {
                return FAILED;
@@ -597,4 +673,3 @@ socket_dynamic_socket_t *socket_dynamic_socket_create()
 
        return &this->public;
 }
-