From 85645a2b82dbb1a414c9a61e481f608141bab86a Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Wed, 12 Feb 2025 17:34:20 +0200 Subject: [PATCH] Support bind to device Using SO_BINDTODEVICE option for the socket. A similar patch was proposed long ago[1], but was rejected. This feature is requested once in a while by several users[2][3]. [1] https://mailman.nginx.org/pipermail/nginx-devel/2013-January/003234.html [2] https://mailman.nginx.org/pipermail/nginx/2016-May/050713.html [3] https://stackoverflow.com/q/39536714/764870 --- auto/unix | 10 ++++++ contrib/vim/syntax/nginx.vim | 2 +- src/core/ngx_connection.c | 50 +++++++++++++++++++++++++++++ src/core/ngx_connection.h | 3 ++ src/core/ngx_module.h | 8 ++++- src/http/ngx_http.c | 4 +++ src/http/ngx_http_core_module.c | 14 ++++++++ src/http/ngx_http_core_module.h | 3 ++ src/stream/ngx_stream.c | 4 +++ src/stream/ngx_stream_core_module.c | 14 ++++++++ 10 files changed, 110 insertions(+), 2 deletions(-) diff --git a/auto/unix b/auto/unix index f29e69c61..0802a2cff 100644 --- a/auto/unix +++ b/auto/unix @@ -354,6 +354,16 @@ ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_BINDANY, NULL, 0)" . auto/feature +ngx_feature="SO_BINDTODEVICE" +ngx_feature_name="NGX_HAVE_BINDTODEVICE" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, SOL_SOCKET, SO_BINDTODEVICE, NULL, 0)" +. auto/feature + + # Linux transparent proxying ngx_feature="IP_TRANSPARENT" diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim index 29eef7a23..f397faec9 100644 --- a/contrib/vim/syntax/nginx.vim +++ b/contrib/vim/syntax/nginx.vim @@ -67,7 +67,7 @@ syn match ngxListenComment '#.*$' syn keyword ngxListenOptions contained \ default_server ssl quic proxy_protocol \ setfib fastopen backlog rcvbuf sndbuf accept_filter deferred bind - \ ipv6only reuseport so_keepalive + \ ipv6only reuseport so_keepalive bind_device \ nextgroup=@ngxListenParams skipwhite skipempty syn keyword ngxListenOptionsDeprecated contained \ http2 diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 75809d9ad..ca4168157 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -8,6 +8,9 @@ #include #include #include +#if (NGX_HAVE_BINDTODEVICE) +#include +#endif ngx_os_io_t ngx_io; @@ -150,6 +153,9 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle) #if (NGX_HAVE_REUSEPORT) int reuseport; #endif +#if (NGX_HAVE_BINDTODEVICE) + char bind_device[IFNAMSIZ]; +#endif ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { @@ -397,6 +403,39 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle) ls[i].deferred_accept = 1; #endif + +#if (NGX_HAVE_BINDTODEVICE) + + ngx_memzero(&bind_device, sizeof(bind_device)); + olen = sizeof(bind_device); + + if (getsockopt(ls[i].fd, SOL_SOCKET, SO_BINDTODEVICE, bind_device, &olen) + == -1) + { + err = ngx_socket_errno; + + if (err == NGX_EINVAL) { + continue; + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, + "getsockopt(SO_BINDTODEVICE) for %V failed, ignored", + &ls[i].addr_text); + continue; + } + + if (bind_device[0] == '\0') { + continue; + } + + ls[i].bind_device = ngx_palloc(cycle->pool, IFNAMSIZ); + if (ls[i].bind_device == NULL) { + return NGX_ERROR; + } + + (void) ngx_cpystrn((u_char *) ls[i].bind_device, + (u_char *) bind_device, IFNAMSIZ); +#endif } return NGX_OK; @@ -495,6 +534,17 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle) return NGX_ERROR; } +#if (NGX_HAVE_BINDTODEVICE) + if (ls[i].bind_device) { + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, + ls[i].bind_device, ngx_strlen(ls[i].bind_device)) == -1) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(SO_BINDTODEVICE, %s) %V failed, ignored", + ls[i].bind_device, &ls[i].addr_text); + } + } +#endif + if (ls[i].type != SOCK_DGRAM || !ngx_test_config) { if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 84dd80442..25dd7ed76 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -81,6 +81,9 @@ struct ngx_listening_s { #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) char *accept_filter; #endif +#if (NGX_HAVE_BINDTODEVICE) + const char *bind_device; +#endif #if (NGX_HAVE_SETFIB) int setfib; #endif diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h index a415cd6d9..890f5273c 100644 --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -202,6 +202,12 @@ #define NGX_MODULE_SIGNATURE_34 "0" #endif +#if (NGX_HAVE_BINDTODEVICE) +#define NGX_MODULE_SIGNATURE_35 "1" +#else +#define NGX_MODULE_SIGNATURE_35 "0" +#endif + #define NGX_MODULE_SIGNATURE \ NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2 \ NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5 \ @@ -214,7 +220,7 @@ NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26 \ NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29 \ NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32 \ - NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 + NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 NGX_MODULE_SIGNATURE_35 #define NGX_MODULE_V1 \ diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index d835f896e..10cd01c99 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1886,6 +1886,10 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->quic = addr->opt.quic; #endif +#if (NGX_HAVE_BINDTODEVICE) + ls->bind_device = addr->opt.bind_device; +#endif + return ls; } diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index a1540c018..2445e4f7f 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4027,6 +4027,20 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strncmp(value[n].data, "bind_device=", 12) == 0) { +#if (NGX_HAVE_BINDTODEVICE) + lsopt.bind_device = (const char *) &value[n].data[12]; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "bind to device \"%V\" is not supported " + "on this platform, ignored", + &value[n]); +#endif + continue; + } + #if (NGX_HAVE_SETFIB) if (ngx_strncmp(value[n].data, "setfib=", 7) == 0) { lsopt.setfib = ngx_atoi(value[n].data + 7, value[n].len - 7); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index e7e266bf8..6de487666 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -103,6 +103,9 @@ typedef struct { #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) char *accept_filter; #endif +#if (NGX_HAVE_BINDTODEVICE) + const char *bind_device; +#endif } ngx_http_listen_opt_t; diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index b6eeb23af..ad1016e04 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -1047,6 +1047,10 @@ ngx_stream_add_listening(ngx_conf_t *cf, ngx_stream_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; +#if (NGX_HAVE_BINDTODEVICE) + ls->bind_device = addr->opt.bind_device; +#endif + return ls; } diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 40951c291..c8dbd0125 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -952,6 +952,20 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strncmp(value[n].data, "bind_device=", 12) == 0) { +#if (NGX_HAVE_BINDTODEVICE) + lsopt.bind_device = (const char *) &value[n].data[12]; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "bind to device \"%V\" is not supported " + "on this platform, ignored", + &value[n]); +#endif + continue; + } + #if (NGX_HAVE_SETFIB) if (ngx_strncmp(value[i].data, "setfib=", 7) == 0) { lsopt.setfib = ngx_atoi(value[i].data + 7, value[i].len - 7);