Subject: Support to bind accepted socket to device on Linux
authorLeonardo Maccari Rufino <eulmr1@gmail.com>
Fri, 2 Jun 2017 17:07:35 +0000 (14:07 -0300)
committerAndy Green <andy@warmcat.com>
Wed, 7 Jun 2017 00:13:11 +0000 (08:13 +0800)
AG: move new member to end of info,
    allow info member even on nonsupporting platform,
    document requires root,
    apply only to listen skt before we drop root,
    add -k to test server to allow testing

README.coding.md
lib/context.c
lib/libwebsockets.h
lib/lws-plat-unix.c
lib/private-libwebsockets.h
test-server/test-server.c

index 33a13f6..9cd9fdd 100644 (file)
@@ -862,6 +862,40 @@ This allocation is only deleted / replaced when the connection accesses a
 URL region with a different protocol (or the default protocols[0] if no
 CALLBACK area matches it).
 
+@section BINDTODEV SO_BIND_TO_DEVICE
+
+The .bind_iface flag in the context / vhost creation struct lets you
+declare that you want all traffic for listen and transport on that
+vhost to be strictly bound to the network interface named in .iface.
+
+This Linux-only feature requires SO_BIND_TO_DEVICE, which in turn
+requires CAP_NET_RAW capability... root has this capability.
+
+However this feature needs to apply the binding also to accepted
+sockets during normal operation, which implies the server must run
+the whole time as root.
+
+You can avoid this by using the Linux capabilities feature to have
+the unprivileged user inherit just the CAP_NET_RAW capability.
+
+You can confirm this with the test server
+
+
+```
+ $ sudo /usr/local/bin/libwebsockets-test-server -u agreen -i eno1 -k
+```
+
+The part that ensures the capability is inherited by the unprivileged
+user is
+
+```
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+                        info.caps[0] = CAP_NET_RAW;
+                        info.count_caps = 1;
+#endif
+```
+
+
 @section dim Dimming webpage when connection lost
 
 The lws test plugins' html provides useful feedback on the webpage about if it
index 248e29b..9bf5465 100644 (file)
@@ -355,6 +355,10 @@ lws_create_vhost(struct lws_context *context,
                vh->name = info->vhost_name;
 
        vh->iface = info->iface;
+#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
+       vh->bind_iface = info->bind_iface;
+#endif
+
        for (vh->count_protocols = 0;
             info->protocols[vh->count_protocols].callback;
             vh->count_protocols++)
index b738554..427fdae 100644 (file)
@@ -1991,6 +1991,17 @@ struct lws_context_creation_info {
        /**< CONTEXT: count of Linux capabilities in .caps[].  0 means
         * no capabilities will be inherited from root (the default) */
 #endif
+       int bind_iface;
+       /**< VHOST: nonzero to strictly bind sockets to the interface name in
+        * .iface (eg, "eth2"), using SO_BIND_TO_DEVICE.
+        *
+        * Requires SO_BINDTODEVICE support from your OS and CAP_NET_RAW
+        * capability.
+        *
+        * Notice that common things like access network interface IP from
+        * your local machine use your lo / loopback interface and will be
+        * disallowed by this.
+        */
 
        /* Add new things just above here ---^
         * This is part of the ABI, don't needlessly break compatibility
index ae73dfc..a2284f4 100644 (file)
@@ -255,6 +255,17 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
 #endif
        }
 
+#if defined(SO_BINDTODEVICE)
+       if (vhost->bind_iface) {
+               lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface);
+               if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface,
+                               strlen(vhost->iface)) < 0) {
+                       lwsl_warn("Failed to bind to device %s\n", vhost->iface);
+                       return 1;
+               }
+       }
+#endif
+
        /* Disable Nagle */
        optval = 1;
 #if defined (__sun)
index 48940bd..993f6e1 100644 (file)
@@ -854,6 +854,9 @@ struct lws_vhost {
        struct lws *lserv_wsi;
        const char *name;
        const char *iface;
+#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
+       int bind_iface;
+#endif
        const struct lws_protocols *protocols;
        void **protocol_vh_privs;
        const struct lws_protocol_vhost_options *pvo;
index 74c37da..d6ddb9d 100644 (file)
@@ -222,7 +222,7 @@ int main(int argc, char **argv)
        info.port = 7681;
 
        while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:C:K:A:R:vu:g:P:", options, NULL);
+               n = getopt_long(argc, argv, "eci:hsap:d:Dr:C:K:A:R:vu:g:P:k", options, NULL);
                if (n < 0)
                        continue;
                switch (n) {
@@ -260,6 +260,13 @@ int main(int argc, char **argv)
                        interface_name[(sizeof interface_name) - 1] = '\0';
                        iface = interface_name;
                        break;
+               case 'k':
+                       info.bind_iface = 1;
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+                       info.caps[0] = CAP_NET_RAW;
+                       info.count_caps = 1;
+#endif
+                       break;
                case 'c':
                        close_testing = 1;
                        fprintf(stderr, " Close testing mode -- closes on "