diff --git a/configlexer.lex b/configlexer.lex index 7acf11d00..4270d1e08 100644 --- a/configlexer.lex +++ b/configlexer.lex @@ -337,6 +337,7 @@ xdp-program-path{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XDP_PROGRAM_PA xdp-program-load{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XDP_PROGRAM_LOAD; } xdp-bpffs-path{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XDP_BPFFS_PATH; } xdp-force-copy{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XDP_FORCE_COPY; } +udp-padding-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_UDP_PADDING_PORT;} {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;} servers={UNQUOTEDLETTER}* { diff --git a/configparser.y b/configparser.y index e8da438c6..d9c5d00ea 100644 --- a/configparser.y +++ b/configparser.y @@ -144,6 +144,7 @@ struct component { %token VAR_METRICS_INTERFACE %token VAR_METRICS_PORT %token VAR_METRICS_PATH +%token VAR_UDP_PADDING_PORT /* dnstap */ %token VAR_DNSTAP @@ -539,7 +540,7 @@ server_option: { cfg_parser->opt->tls_cert_bundle = region_strdup(cfg_parser->opt->region, $2); } | VAR_PROXY_PROTOCOL_PORT number { - struct proxy_protocol_port_list* elem = region_alloc_zero( + struct port_list* elem = region_alloc_zero( cfg_parser->opt->region, sizeof(*elem)); elem->port = $2; elem->next = cfg_parser->opt->proxy_protocol_port; @@ -681,6 +682,14 @@ server_option: cfg_parser->opt->metrics_path = region_strdup(cfg_parser->opt->region, $2); #endif /* USE_METRICS */ } + | VAR_UDP_PADDING_PORT number + { + struct port_list* elem = region_alloc_zero( + cfg_parser->opt->region, sizeof(*elem)); + elem->port = $2; + elem->next = cfg_parser->opt->udp_padding_port; + cfg_parser->opt->udp_padding_port = elem; + } ; socket_options: diff --git a/edns.c b/edns.c index d77a146ef..0458108ff 100644 --- a/edns.c +++ b/edns.c @@ -20,6 +20,7 @@ #include "edns.h" #include "nsd.h" #include "query.h" +#include "options.h" #if !defined(HAVE_SSL) || !defined(HAVE_CRYPTO_MEMCMP) /* we need fixed time compare, pull it in from tsig.c */ @@ -119,7 +120,7 @@ edns_handle_option(uint16_t optcode, uint16_t optlen, buffer_type* packet, } break; case PADDING_CODE: - if(query->tls) + if(query->tcp || query->may_pad) edns->padding = 1; buffer_skip(packet, optlen); break; diff --git a/nsd-checkconf.c b/nsd-checkconf.c index 246a011c0..2305d33da 100644 --- a/nsd-checkconf.c +++ b/nsd-checkconf.c @@ -512,11 +512,17 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o, return; } if(strcasecmp(o, "proxy_protocol_port") == 0) { - struct proxy_protocol_port_list* p; + struct port_list* p; for(p = opt->proxy_protocol_port; p; p = p->next) printf("%d\n", p->port); return; } + if(strcasecmp(o, "udp_padding_port") == 0) { + struct port_list* p; + for(p = opt->udp_padding_port; p; p = p->next) + printf("%d\n", p->port); + return; + } printf("Server option not handled: %s\n", o); exit(1); } @@ -735,10 +741,15 @@ config_test_print_server(nsd_options_type* opt) print_string_var("cookie-secret-file:", ""); } if(opt->proxy_protocol_port) { - struct proxy_protocol_port_list* p; + struct port_list* p; for(p = opt->proxy_protocol_port; p; p = p->next) printf("\tproxy-protocol-port: %d\n", p->port); } + if(opt->udp_padding_port) { + struct port_list* p; + for(p = opt->udp_padding_port; p; p = p->next) + printf("\tudp-padding-port: %d\n", p->port); + } #ifdef USE_METRICS printf("\tmetrics-enable: %s\n", opt->metrics_enable?"yes":"no"); diff --git a/nsd.conf.5.in b/nsd.conf.5.in index 261d401f2..43c877047 100644 --- a/nsd.conf.5.in +++ b/nsd.conf.5.in @@ -634,6 +634,11 @@ times, additional port numbers can be used for proxy protocol service. The interface definitions that use this port number expect PROXYv2 proxy protocol traffic, for UDP, TCP and for TLS service. .TP +.B udp\-padding\-port:\fR +UDP requests received over this port containing an EDNS0 padding option will, +unlike ordinary UDP requests, be answered with a padded response. The option +may be given more than once. +.TP .B xdp\-interface:\fR The interface to use XDP with. This enables the use of AF_XDP sockets for UDP queries. Default is "", disabled. (EXPERIMENTAL) diff --git a/options.c b/options.c index ccfa13ebf..d35cd37c4 100644 --- a/options.c +++ b/options.c @@ -152,6 +152,7 @@ nsd_options_create(region_type* region) opt->tls_cert_bundle = NULL; opt->tls_auth_xfr_only = 0; opt->proxy_protocol_port = NULL; + opt->udp_padding_port = NULL; opt->answer_cookie = 0; opt->cookie_secret = NULL; opt->cookie_staging_secret = NULL; @@ -3173,10 +3174,8 @@ resolve_interface_names(struct nsd_options* options) } int -sockaddr_uses_proxy_protocol_port(struct nsd_options* options, - struct sockaddr* addr) +sockaddr_uses_port(struct sockaddr* addr, struct port_list* p) { - struct proxy_protocol_port_list* p; int port; #ifdef INET6 struct sockaddr_storage* ss = (struct sockaddr_storage*)addr; @@ -3196,7 +3195,6 @@ sockaddr_uses_proxy_protocol_port(struct nsd_options* options, return 0; /* unknown family */ } #endif - p = options->proxy_protocol_port; while(p) { if(p->port == port) return 1; diff --git a/options.h b/options.h index eda716f20..4da0b5210 100644 --- a/options.h +++ b/options.h @@ -21,7 +21,7 @@ struct dname; struct tsig_key; struct buffer; struct nsd; -struct proxy_protocol_port_list; +struct port_list; typedef struct nsd_options nsd_options_type; @@ -147,7 +147,9 @@ struct nsd_options { int tls_auth_xfr_only; /* proxy protocol port list */ - struct proxy_protocol_port_list* proxy_protocol_port; + struct port_list* proxy_protocol_port; + /* udp-padding-port list */ + struct port_list* udp_padding_port; /** remote control section. enable toggle. */ int control_enable; @@ -463,9 +465,9 @@ struct tls_auth_options { char* client_key_pw; }; -/* proxy protocol port option list */ -struct proxy_protocol_port_list { - struct proxy_protocol_port_list* next; +/* port option list */ +struct port_list { + struct port_list* next; int port; }; @@ -666,8 +668,7 @@ void warn_if_directory(const char* filetype, FILE* f, const char* fname); * names. */ void resolve_interface_names(struct nsd_options* options); -/* See if the sockaddr port number is listed in the proxy protocol ports. */ -int sockaddr_uses_proxy_protocol_port(struct nsd_options* options, - struct sockaddr* addr); +/* See if the sockaddr port number is listed in the ports. */ +int sockaddr_uses_port(struct sockaddr* addr, struct port_list* ports); #endif /* OPTIONS_H */ diff --git a/query.c b/query.c index f94a503eb..97b48cce1 100644 --- a/query.c +++ b/query.c @@ -250,6 +250,7 @@ query_reset(query_type *q, size_t maxlen, int is_tcp) q->remote_addrlen = (socklen_t)sizeof(q->remote_addr); q->client_addrlen = (socklen_t)sizeof(q->client_addr); q->is_proxied = 0; + q->may_pad = 0; q->maxlen = maxlen; q->reserved_space = 0; buffer_clear(q->packet); diff --git a/query.h b/query.h index e17f94672..7a09dc971 100644 --- a/query.h +++ b/query.h @@ -47,6 +47,9 @@ struct query { /* if set, the request came through a proxy */ int is_proxied; + /* if set, the response may be padded */ + int may_pad; + /* the client address * the same as remote_addr if not proxied */ #ifdef INET6 diff --git a/server.c b/server.c index ccf6e2dcc..77b4a9a93 100644 --- a/server.c +++ b/server.c @@ -158,6 +158,8 @@ struct udp_handler_data struct event event; /* if set, PROXYv2 is expected on this connection */ int pp2_enabled; + /* if set, padding is allowed on this connection */ + int may_pad; }; struct tcp_accept_handler_data { @@ -3331,11 +3333,14 @@ add_udp_handler( data->nsd = nsd; data->socket = sock; - if(nsd->options->proxy_protocol_port && - sockaddr_uses_proxy_protocol_port(nsd->options, - (struct sockaddr *)&sock->addr.ai_addr)) { + if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr, + nsd->options->proxy_protocol_port)) { data->pp2_enabled = 1; } + if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr, + nsd->options->udp_padding_port)) { + data->may_pad = 1; + } memset(handler, 0, sizeof(*handler)); event_set(handler, sock->s, EV_PERSIST|EV_READ, handle_udp, data); @@ -3356,9 +3361,8 @@ add_tcp_handler( data->nsd = nsd; data->socket = sock; - if(nsd->options->proxy_protocol_port && - sockaddr_uses_proxy_protocol_port(nsd->options, - (struct sockaddr *)&sock->addr.ai_addr)) { + if(sockaddr_uses_port((struct sockaddr *)&sock->addr.ai_addr, + nsd->options->proxy_protocol_port)) { data->pp2_enabled = 1; } @@ -4173,6 +4177,7 @@ handle_udp(int fd, short event, void* arg) queries[i]->remote_addrlen = msgs[i].msg_hdr.msg_namelen; queries[i]->client_addrlen = (socklen_t)sizeof(queries[i]->client_addr); queries[i]->is_proxied = 0; + queries[i]->may_pad = data->may_pad; q = queries[i]; if (received == -1) { log_msg(LOG_ERR, "recvmmsg %d failed %s", i, strerror( diff --git a/tpkg/dot_padding.tdir/dot_padding.dsc b/tpkg/dot_padding.tdir/dot_padding.dsc deleted file mode 100644 index 964faeed6..000000000 --- a/tpkg/dot_padding.tdir/dot_padding.dsc +++ /dev/null @@ -1,16 +0,0 @@ -BaseName: dot_padding -Version: 1.0 -Description: Test EDNS0 padding option -CreationDate: do 2 jul 2026 12:00:23 CEST -Maintainer: Willem Toorop -Category: -Component: -CmdDepends: -Depends: -Help: -Pre: dot_padding.pre -Post: dot_padding.post -Test: dot_padding.test -AuxFiles: dot_padding.conf.in dot_padding.zone dot_padding.key dot_padding.pem -Passed: -Failure: diff --git a/tpkg/dot_padding.tdir/dot_padding.conf.in b/tpkg/padding.tdir/padding.conf.in similarity index 55% rename from tpkg/dot_padding.tdir/dot_padding.conf.in rename to tpkg/padding.tdir/padding.conf.in index 6c9eedec8..66388af51 100644 --- a/tpkg/dot_padding.tdir/dot_padding.conf.in +++ b/tpkg/padding.tdir/padding.conf.in @@ -2,13 +2,15 @@ server: zonesdir: "" verbosity: 2 ip-address: 127.0.0.1 + ip-address: 127.0.0.1@@UDP_PAD_PORT@ ip-address: 127.0.0.1@@TPKG_DOT_PORT@ + udp-padding-port: @UDP_PAD_PORT@ tls-port: @TPKG_DOT_PORT@ xfrdfile: "xfrd.state" zonelistfile: "zone.list" - tls-service-key: "dot_padding.key" - tls-service-pem: "dot_padding.pem" + tls-service-key: "padding.key" + tls-service-pem: "padding.pem" zone: name: "example.com" - zonefile: "dot_padding.zone" + zonefile: "padding.zone" diff --git a/tpkg/padding.tdir/padding.dsc b/tpkg/padding.tdir/padding.dsc new file mode 100644 index 000000000..3ca6664c6 --- /dev/null +++ b/tpkg/padding.tdir/padding.dsc @@ -0,0 +1,16 @@ +BaseName: padding +Version: 1.0 +Description: Test EDNS0 padding option +CreationDate: za 25 jul 2026 12:48:29 CEST +Maintainer: Willem Toorop +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: padding.pre +Post: padding.post +Test: padding.test +AuxFiles: padding.conf.in padding.zone padding.key padding.pem +Passed: +Failure: diff --git a/tpkg/dot_padding.tdir/dot_padding.help b/tpkg/padding.tdir/padding.help similarity index 100% rename from tpkg/dot_padding.tdir/dot_padding.help rename to tpkg/padding.tdir/padding.help diff --git a/tpkg/dot_padding.tdir/dot_padding.key b/tpkg/padding.tdir/padding.key similarity index 100% rename from tpkg/dot_padding.tdir/dot_padding.key rename to tpkg/padding.tdir/padding.key diff --git a/tpkg/dot_padding.tdir/dot_padding.pem b/tpkg/padding.tdir/padding.pem similarity index 100% rename from tpkg/dot_padding.tdir/dot_padding.pem rename to tpkg/padding.tdir/padding.pem diff --git a/tpkg/dot_padding.tdir/dot_padding.post b/tpkg/padding.tdir/padding.post similarity index 93% rename from tpkg/dot_padding.tdir/dot_padding.post rename to tpkg/padding.tdir/padding.post index f99b91e89..3419501a2 100644 --- a/tpkg/dot_padding.tdir/dot_padding.post +++ b/tpkg/padding.tdir/padding.post @@ -1,4 +1,4 @@ -# #-- dot_padding.post --# +# #-- padding.post --# # source the master var file when it's there [ -f ../.tpkg.var.master ] && source ../.tpkg.var.master # source the test var file when it's there diff --git a/tpkg/dot_padding.tdir/dot_padding.pre b/tpkg/padding.tdir/padding.pre similarity index 67% rename from tpkg/dot_padding.tdir/dot_padding.pre rename to tpkg/padding.tdir/padding.pre index 880d917c5..7ed99073a 100644 --- a/tpkg/dot_padding.tdir/dot_padding.pre +++ b/tpkg/padding.tdir/padding.pre @@ -1,4 +1,4 @@ -# #-- dot_padding.pre--# +# #-- padding.pre--# # source the master var file when it's there [ -f ../.tpkg.var.master ] && source ../.tpkg.var.master # use .tpkg.var.test for in test variable passing @@ -6,9 +6,10 @@ . ../common.sh # start NSD -get_random_port 2 +get_random_port 3 TPKG_PORT=$RND_PORT -TPKG_DOT_PORT=`expr $RND_PORT + 1` +UDP_PAD_PORT=`expr $RND_PORT + 1` +TPKG_DOT_PORT=`expr $RND_PORT + 2` PRE="../.." TPKG_NSD_PID="$PRE/nsd.pid.$$" @@ -16,10 +17,12 @@ TPKG_NSD="$PRE/nsd" # share the vars echo "export TPKG_PORT=$TPKG_PORT" >> .tpkg.var.test +echo "export UDP_PAD_PORT=$UDP_PAD_PORT" >> .tpkg.var.test echo "export TPKG_DOT_PORT=$TPKG_DOT_PORT" >> .tpkg.var.test echo "export TPKG_NSD_PID=$TPKG_NSD_PID" >> .tpkg.var.test -sed -e "s/@TPKG_DOT_PORT@/$TPKG_DOT_PORT/" < dot_padding.conf.in > edit.conf +sed -e "s/@TPKG_DOT_PORT@/$TPKG_DOT_PORT/" \ + -e "s/@UDP_PAD_PORT@/$UDP_PAD_PORT/" < padding.conf.in > edit.conf $TPKG_NSD -c edit.conf -u "" -l nsd.log -p $TPKG_PORT -P $TPKG_NSD_PID wait_nsd_up nsd.log diff --git a/tpkg/dot_padding.tdir/dot_padding.test b/tpkg/padding.tdir/padding.test similarity index 59% rename from tpkg/dot_padding.tdir/dot_padding.test rename to tpkg/padding.tdir/padding.test index e8cf8f7d7..cff328239 100644 --- a/tpkg/dot_padding.tdir/dot_padding.test +++ b/tpkg/padding.tdir/padding.test @@ -1,4 +1,4 @@ -# #-- dot_padding.test --# +# #-- padding.test --# # source the master var file when it's there [ -f ../.tpkg.var.master ] && source ../.tpkg.var.master # use .tpkg.var.test for in test variable passing @@ -33,12 +33,20 @@ do echo "Error: transport was not TCP" exit 1 fi - if grep -q '^; PAD:' TCP-${SIZE}.out; then - cat TCP-${SIZE}.out - echo "Error: padding option seen in TCP message" - exit 1 + if [ $SIZE -eq $PADDED ]; then + if grep -q '^; PAD:' TCP-${SIZE}.out; then + cat TCP-${SIZE}.out + echo "Error: padding option seen in TCP message of size $SIZE" + exit 1 + fi + else + if ! grep -q '^; PAD:' TCP-${SIZE}.out; then + cat TCP-${SIZE}.out + echo "Error: padding option not seen in TCP message" + exit 1 + fi fi - if ! grep -q "^;; MSG SIZE[[:space:]][[:space:]]*rcvd: ${SIZE}$" TCP-${SIZE}.out; then + if ! grep -q "^;; MSG SIZE[[:space:]][[:space:]]*rcvd: ${PADDED}$" TCP-${SIZE}.out; then cat TCP-${SIZE}.out echo "Error: TCP message has wrong message size" exit 1 @@ -50,7 +58,7 @@ do echo "Error: transport was not TLS" exit 1 fi - if [ $SIZE -eq 468 -o $SIZE -eq 936 ]; then + if [ $SIZE -eq $PADDED ]; then if grep -q '^; PAD:' TLS-${SIZE}.out; then cat TLS-${SIZE}.out echo "Error: padding option seen in TLS message of size $SIZE" @@ -68,5 +76,30 @@ do echo "Error: TLS message has wrong message size (should have been $PADDED)" exit 1 fi + + dig @127.0.0.1 -p $UDP_PAD_PORT ${SIZE}.example.com. TXT +padding=128 2>&1 > UDP-PAD-${SIZE}.out + if ! grep -q ';; SERVER:.*(UDP)$' UDP-PAD-${SIZE}.out; then + cat UDP-PAD-${SIZE}.out + echo "Error: transport was not UDP for UDP padding allowed port" + exit 1 + fi + if [ $SIZE -eq $PADDED ]; then + if grep -q '^; PAD:' UDP-PAD-${SIZE}.out; then + cat UDP-PAD-${SIZE}.out + echo "Error: padding option seen in UDP padding allowed message of size $SIZE" + exit 1 + fi + else + if ! grep -q '^; PAD:' UDP-PAD-${SIZE}.out; then + cat UDP-PAD-${SIZE}.out + echo "Error: padding option not seen in UDP padding allowed message" + exit 1 + fi + fi + if ! grep -q "^;; MSG SIZE[[:space:]][[:space:]]*rcvd: ${PADDED}$" UDP-PAD-${SIZE}.out; then + cat UDP-PAD-${SIZE}.out + echo "Error: UDP padding allowed message has wrong message size (should have been $PADDED)" + exit 1 + fi done diff --git a/tpkg/dot_padding.tdir/dot_padding.zone b/tpkg/padding.tdir/padding.zone similarity index 100% rename from tpkg/dot_padding.tdir/dot_padding.zone rename to tpkg/padding.tdir/padding.zone