From d6b4254a1622b3e7df795fd5b1070d4f84cf55d2 Mon Sep 17 00:00:00 2001 From: Alexandre Cassen Date: Thu, 21 Nov 2024 19:01:10 +0100 Subject: [PATCH] resolv: Add support to local bind while connecting remote nameserver. This feature is available via following VTY command in apn conf : "nameserver-bind (A.B.C.D|X:X:X:X) port <1024-65535>" This feature is useful when your local networking configuration is based on loopback interfaces (dummy). In that situation connected interface and service interface are different so you need to bind to the proper interface for service. glibc resolver interface (not documented), is providing context structure in order to set remote nameserver and connection to use. We are then, creating our nameserver resolv socket directly, bind to the proper Address & Port, and pass it to glibc as pre-init. --- src/gtp_apn.c | 37 +++++++++++++++++++ src/gtp_resolv.c | 77 ++++++++++++++++++++++++++++++++++++++++ src/gtp_server.c | 10 +++--- src/include/gtp_apn.h | 1 + src/include/gtp_resolv.h | 3 +- 5 files changed, 121 insertions(+), 7 deletions(-) diff --git a/src/gtp_apn.c b/src/gtp_apn.c index ffbae29..b3f9cb4 100644 --- a/src/gtp_apn.c +++ b/src/gtp_apn.c @@ -535,6 +535,37 @@ DEFUN(apn_nameserver, return CMD_SUCCESS; } +DEFUN(apn_nameserver_bind, + apn_nameserver_bind_cmd, + "nameserver-bind (A.B.C.D|X:X:X:X) port <1024-65535>", + "Set Global PDN nameserver binding Address\n" + "IP Address\n" + "IPv4 Address\n" + "IPv6 Address\n" + "UDP Port\n" + "Number\n") +{ + gtp_apn_t *apn = vty->index; + struct sockaddr_storage *addr = &apn->nameserver_bind; + int ret, port; + + if (argc < 2) { + vty_out(vty, "%% missing arguments%s", VTY_NEWLINE); + return CMD_WARNING; + } + + VTY_GET_INTEGER_RANGE("UDP Port", port, argv[1], 1024, 65535); + + ret = inet_stosockaddr(argv[0], port, addr); + if (ret < 0) { + vty_out(vty, "%% malformed IP address %s%s", argv[0], VTY_NEWLINE); + memset(addr, 0, sizeof(struct sockaddr_storage)); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + DEFUN(apn_nameserver_timeout, apn_nameserver_timeout_cmd, "nameserver-timeout INTEGER", @@ -1250,6 +1281,11 @@ apn_config_write(vty_t *vty) vty_out(vty, " nameserver %s%s" , inet_sockaddrtos(&apn->nameserver) , VTY_NEWLINE); + if (apn->nameserver_bind.ss_family) + vty_out(vty, " nameserver-bind %s port %d%s" + , inet_sockaddrtos(&apn->nameserver_bind) + , ntohs(inet_sockaddrport(&apn->nameserver_bind)) + , VTY_NEWLINE); if (apn->nameserver_timeout) vty_out(vty, " nameserver-timeout %d%s" , apn->nameserver_timeout @@ -1310,6 +1346,7 @@ gtp_apn_vty_init(void) install_default(APN_NODE); install_element(APN_NODE, &apn_nameserver_cmd); + install_element(APN_NODE, &apn_nameserver_bind_cmd); install_element(APN_NODE, &apn_nameserver_timeout_cmd); install_element(APN_NODE, &apn_resolv_max_retry_cmd); install_element(APN_NODE, &apn_resolv_cache_update_cmd); diff --git a/src/gtp_resolv.c b/src/gtp_resolv.c index deb503e..24b9050 100644 --- a/src/gtp_resolv.c +++ b/src/gtp_resolv.c @@ -103,6 +103,80 @@ ns_log_error(const char *dn, int error) } } +static int +ns_bind_connect(gtp_apn_t *apn) +{ + struct sockaddr_storage *addr = &apn->nameserver_bind; + socklen_t addrlen; + int fd, err; + + if (!apn->nameserver_bind.ss_family) + return -1; + + /* Create UDP Client socket */ + fd = socket(addr->ss_family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + fd = (fd < 0) ? fd : if_setsockopt_reuseaddr(fd, 1); + fd = (fd < 0) ? fd : if_setsockopt_rcvtimeo(fd, 2000); + fd = (fd < 0) ? fd : if_setsockopt_sndtimeo(fd, 2000); + if (fd < 0) { + log_message(LOG_INFO, "%s(): error creating UDP [%s]:%d socket" + , __FUNCTION__ + , inet_sockaddrtos(addr) + , ntohs(inet_sockaddrport(addr))); + return -1; + } + + /* Bind listening channel */ + addrlen = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6); + err = bind(fd, (struct sockaddr *) addr, addrlen); + if (err) { + log_message(LOG_INFO, "%s(): Error binding to [%s]:%d (%m)" + , __FUNCTION__ + , inet_sockaddrtos(addr) + , ntohs(inet_sockaddrport(addr))); + close(fd); + return -1; + } + + err = connect(fd, (struct sockaddr *) &apn->nameserver, addrlen); + if (err) { + log_message(LOG_INFO, "%s(): Error connecting to [%s]:%d (%m)" + , __FUNCTION__ + , inet_sockaddrtos(&apn->nameserver) + , ntohs(inet_sockaddrport(&apn->nameserver))); + close(fd); + return -1; + } + + return fd; +} + +static int +ns_ctx_init(gtp_resolv_ctx_t *ctx) +{ + gtp_apn_t *apn = ctx->apn; + struct sockaddr_storage *addr; + int fd; + + fd = ns_bind_connect(apn); + if (fd < 0) + return -1; + + addr = (apn->nameserver.ss_family) ? &apn->nameserver : &daemon_data->nameserver; + + /* glibc resolver is providing extension to set remote nameserver. + * We are using this facility to set pre-allocated/pre-initialized + * socket connection to remote nameserver. Specially useful when you + * want to bind the connection to a local IP Address. */ + ctx->ns_rs._u._ext.nssocks[0] = fd; + ctx->ns_rs._u._ext.nsaddrs[0] = MALLOC(sizeof(struct sockaddr_in6)); + *ctx->ns_rs._u._ext.nsaddrs[0] = *((struct sockaddr_in6 *) addr); + ctx->ns_rs._u._ext.nscount = 1; + return 0; +} + + static int ns_res_nquery_retry(gtp_resolv_ctx_t *ctx, int class, int type) { @@ -110,6 +184,7 @@ ns_res_nquery_retry(gtp_resolv_ctx_t *ctx, int class, int type) int ret; retry: + ns_ctx_init(ctx); ret = res_nquery(&ctx->ns_rs, ctx->nsdisp, class, type, ctx->nsbuffer, GTP_RESOLV_BUFFER_LEN); if (ret < 0) { ns_log_error(ctx->nsdisp, h_errno); @@ -375,6 +450,7 @@ gtp_resolv_naptr(gtp_resolv_ctx_t *ctx, list_head_t *l, const char *format, ...) return 0; } + gtp_resolv_ctx_t * gtp_resolv_ctx_alloc(gtp_apn_t *apn) { @@ -385,6 +461,7 @@ gtp_resolv_ctx_alloc(gtp_apn_t *apn) if (!ctx) return NULL; + ctx->apn = apn; ctx->max_retry = apn->resolv_max_retry; addr = (apn->nameserver.ss_family) ? &apn->nameserver : &daemon_data->nameserver; if (!addr->ss_family) { diff --git a/src/gtp_server.c b/src/gtp_server.c index 7b998fd..59fb560 100644 --- a/src/gtp_server.c +++ b/src/gtp_server.c @@ -90,25 +90,23 @@ gtp_server_udp_init(gtp_server_t *srv) , __FUNCTION__ , inet_sockaddrtos(addr) , ntohs(inet_sockaddrport(addr))); - goto socket_error; + return -1; } /* Bind listening channel */ addrlen = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); err = bind(fd, (struct sockaddr *) addr, addrlen); - if (err < 0) { + if (err) { log_message(LOG_INFO, "%s(): Error binding to [%s]:%d (%m)" , __FUNCTION__ , inet_sockaddrtos(addr) , ntohs(inet_sockaddrport(addr))); - goto socket_error; + close(fd); + return -1; } return fd; - - socket_error: - return -1; } static void * diff --git a/src/include/gtp_apn.h b/src/include/gtp_apn.h index 11e9583..acba305 100644 --- a/src/include/gtp_apn.h +++ b/src/include/gtp_apn.h @@ -84,6 +84,7 @@ typedef struct _gtp_apn { char name[GTP_APN_MAX_LEN]; char realm[GTP_REALM_LEN]; struct sockaddr_storage nameserver; + struct sockaddr_storage nameserver_bind; int nameserver_timeout; uint8_t resolv_max_retry; int resolv_cache_update; diff --git a/src/include/gtp_resolv.h b/src/include/gtp_resolv.h index eff541d..26f5c4b 100644 --- a/src/include/gtp_resolv.h +++ b/src/include/gtp_resolv.h @@ -39,7 +39,7 @@ typedef struct _gtp_pgw { uint16_t priority; uint16_t weight; char srv_name[GTP_DISPLAY_SRV_LEN]; - struct _gtp_naptr *naptr; /*Back-pointer */ + struct _gtp_naptr *naptr; /* Back-pointer */ struct sockaddr_storage addr; uint64_t cnt; time_t last_resp; @@ -71,6 +71,7 @@ typedef struct _gtp_service { } gtp_service_t; typedef struct _gtp_resolv_ctx { + gtp_apn_t *apn; /* Back-pointer */ char *realm; struct __res_state ns_rs; ns_msg msg;