Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
26 July 2026: Jared
- Fix #364: Make TCP and AXFR-over-TLS transfers honor
outgoing-interface. Fix SO_REUSEADDR when binding the local
source address, and key TCP/TLS pipeline reuse by destination
and local source so connections are not shared across
different outgoing interfaces.

16 July 2026: Willem
- Merge #500: Support for the "oots" SVCB Service Parameter Key

Expand Down
59 changes: 52 additions & 7 deletions xfrd-tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,26 @@ int password_cb(char *buf, int size, int ATTR_UNUSED(rwflag), void *u)

#endif

/* sort tcppipe, first on IP address, for an IPaddresss, sort on num_unused */
/* Fill addr with the first outgoing-interface matching acl's address family.
* Returns sockaddr length, or 0 if none. Clears addr when returning 0. */
static socklen_t
xfrd_select_outgoing_sockaddr(struct acl_options* ifc, struct acl_options* acl,
#ifdef INET6
struct sockaddr_storage* addr)
#else
struct sockaddr_in* addr)
#endif /* INET6 */
{
memset(addr, 0, sizeof(*addr));
while(ifc) {
if(ifc->is_ipv6 == acl->is_ipv6)
return xfrd_acl_sockaddr_frm(ifc, addr);
ifc = ifc->next;
}
return 0;
}

/* sort tcppipe, first on destination IP, then source IP, then num_unused */
static int
xfrd_pipe_cmp(const void* a, const void* b)
{
Expand All @@ -246,6 +265,14 @@ xfrd_pipe_cmp(const void* a, const void* b)
r = memcmp(&x->key.ip, &y->key.ip, x->key.ip_len);
if(r != 0)
return r;
/* also match on local (outgoing-interface) source address */
if(y->key.src_len != x->key.src_len)
return (int)y->key.src_len - (int)x->key.src_len;
if(x->key.src_len != 0) {
r = memcmp(&x->key.src, &y->key.src, x->key.src_len);
if(r != 0)
return r;
}
/* sort that num_unused is sorted ascending, */
if(x->key.num_unused != y->key.num_unused) {
return (x->key.num_unused < y->key.num_unused) ? -1 : 1;
Expand Down Expand Up @@ -636,6 +663,9 @@ pipeline_find(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
struct xfrd_tcp_pipeline_key k, *key=&k;
key->node.key = key;
key->ip_len = xfrd_acl_sockaddr_to(zone->master, &key->ip);
key->src_len = xfrd_select_outgoing_sockaddr(
zone->zone_options->pattern->outgoing_interface,
zone->master, &key->src);
key->num_unused = set->tcp_pipeline;
/* lookup existing tcp transfer to the master with highest unused */
if(rbtree_find_less_equal(set->pipetree, key, &sme)) {
Expand All @@ -650,6 +680,12 @@ pipeline_find(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
return NULL;
if(memcmp(&r->key.ip, &key->ip, key->ip_len) != 0)
return NULL;
/* also require the same outgoing-interface (source address) */
if(r->key.src_len != key->src_len)
return NULL;
if(key->src_len != 0 &&
memcmp(&r->key.src, &key->src, key->src_len) != 0)
return NULL;
/* correct master, is there a slot free for this transfer? */
if(r->key.num_unused == 0)
return NULL;
Expand Down Expand Up @@ -970,10 +1006,13 @@ xfrd_tcp_open(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp,
}

tp->key.ip_len = xfrd_acl_sockaddr_to(zone->master, &tp->key.ip);
tp->key.src_len = 0;
memset(&tp->key.src, 0, sizeof(tp->key.src));

/* bind it */
/* bind it (records the successful outgoing-interface in tp->key.src) */
if (!xfrd_bind_local_interface(fd, zone->zone_options->pattern->
outgoing_interface, zone->master, 1)) {
outgoing_interface, zone->master, 1,
&tp->key.src, &tp->key.src_len)) {
close(fd);
xfrd_set_refresh_now(zone);
return 0;
Expand Down Expand Up @@ -1702,13 +1741,19 @@ xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
* for the same master, and can fill the unused ID */
if(tp->key.num_unused == 1 && set->tcp_waiting_first) {
#ifdef INET6
struct sockaddr_storage to;
struct sockaddr_storage to, src;
#else
struct sockaddr_in to;
struct sockaddr_in to, src;
#endif
socklen_t to_len = xfrd_acl_sockaddr_to(
set->tcp_waiting_first->master, &to);
if(to_len == tp->key.ip_len && memcmp(&to, &tp->key.ip, to_len) == 0) {
socklen_t src_len = xfrd_select_outgoing_sockaddr(
set->tcp_waiting_first->zone_options->pattern->
outgoing_interface, set->tcp_waiting_first->master,
&src);
if(to_len == tp->key.ip_len && memcmp(&to, &tp->key.ip, to_len) == 0 &&
src_len == tp->key.src_len &&
(src_len == 0 || memcmp(&src, &tp->key.src, src_len) == 0)) {
/* use this connection for the waiting zone */
zone = set->tcp_waiting_first;
assert(zone->tcp_conn == -1);
Expand All @@ -1720,7 +1765,7 @@ xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
pipeline_setup_new_zone(set, tp, zone);
return;
}
/* waiting zone did not go to same server */
/* waiting zone did not go to same server/source */
}

/* if all unused, or only skipped leftover, close the pipeline */
Expand Down
11 changes: 9 additions & 2 deletions xfrd-tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ struct xfrd_tcp_pipeline_id {
};

/**
* The tcp pipeline key structure. By ip_len, ip, num_unused and unique by
* pointer value.
* The tcp pipeline key structure. By ip_len, ip, src_len, src, num_unused
* and unique by pointer value.
*/
struct xfrd_tcp_pipeline_key {
/* the rbtree node, sorted by IP and nr of unused queries */
Expand All @@ -108,6 +108,13 @@ struct xfrd_tcp_pipeline_key {
struct sockaddr_in ip;
#endif /* INET6 */
socklen_t ip_len;
/* local (outgoing-interface) source address; src_len 0 if unbound */
#ifdef INET6
struct sockaddr_storage src;
#else
struct sockaddr_in src;
#endif /* INET6 */
socklen_t src_len;
/* number of unused IDs. used IDs are waiting to send their query,
* or have been sent but not not all answer packets have been received.
* Sorted by num_unused, so a lookup smaller-equal for 65536 finds the
Expand Down
27 changes: 23 additions & 4 deletions xfrd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ xfrd_send_udp(struct acl_options* acl, buffer_type* packet,
}

/* bind it */
if (!xfrd_bind_local_interface(fd, ifc, acl, 0)) {
if (!xfrd_bind_local_interface(fd, ifc, acl, 0, NULL, NULL)) {
log_msg(LOG_ERR, "xfrd: cannot bind outgoing interface '%s' to "
"udp socket: No matching ip addresses found",
ifc->ip_address_spec);
Expand All @@ -1865,7 +1865,13 @@ xfrd_send_udp(struct acl_options* acl, buffer_type* packet,

int
xfrd_bind_local_interface(int sockd, struct acl_options* ifc,
struct acl_options* acl, int tcp)
struct acl_options* acl, int tcp,
#ifdef INET6
struct sockaddr_storage* bound_addr,
#else
struct sockaddr_in* bound_addr,
#endif
socklen_t* bound_len)
{
#ifdef SO_LINGER
struct linger linger = {1, 0};
Expand All @@ -1878,6 +1884,11 @@ xfrd_bind_local_interface(int sockd, struct acl_options* ifc,
#endif /* INET6 */
int ret = 1;

if(bound_len)
*bound_len = 0;
if(bound_addr)
memset(bound_addr, 0, sizeof(*bound_addr));

if (!ifc) /* no outgoing interface set */
return 1;

Expand All @@ -1895,8 +1906,11 @@ xfrd_bind_local_interface(int sockd, struct acl_options* ifc,

if (tcp) {
#ifdef SO_REUSEADDR
if (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, &frm,
frm_len) < 0) {
/* Enable address reuse so a fixed source port can be
* rebound after a previous TCP/TLS connection. */
int on = 1;
if (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, &on,
sizeof(on)) < 0) {
VERBOSITY(2, (LOG_WARNING, "xfrd: setsockopt "
"SO_REUSEADDR failed: %s", strerror(errno)));
}
Expand Down Expand Up @@ -1924,6 +1938,11 @@ xfrd_bind_local_interface(int sockd, struct acl_options* ifc,
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "xfrd: bind() %s to %s "
"socket was successful",
ifc->ip_address_spec, tcp? "tcp":"udp"));
if(bound_addr) {
memcpy(bound_addr, &frm, frm_len);
if(bound_len)
*bound_len = frm_len;
}
return 1;
}

Expand Down
12 changes: 10 additions & 2 deletions xfrd.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,17 @@ xfrd_prepare_updates_for_reload(void);
*/
void xfrd_prepare_zones_for_reload(void);

/* Bind a local interface to a socket descriptor, return 1 on success */
/* Bind a local interface to a socket descriptor, return 1 on success.
* If bound_addr is non-NULL, it is filled with the source address that was
* bound (or cleared when no outgoing-interface matched / was configured). */
int xfrd_bind_local_interface(int sockd, struct acl_options* ifc,
struct acl_options* acl, int tcp);
struct acl_options* acl, int tcp,
#ifdef INET6
struct sockaddr_storage* bound_addr,
#else
struct sockaddr_in* bound_addr,
#endif
socklen_t* bound_len);

/* process results and soa info from reload */
void xfrd_process_task_result(xfrd_state_type* xfrd, struct udb_base* taskudb);
Expand Down
Loading