tcp: Manage outbound address via flow table

For now when we forward a connection to the host we leave the host side
forwarding address and port blank since we don't necessarily know what
source address and port will be used by the kernel.  When the outbound
address option is active, though, we do know the address at least, so we
can record it in the flowside.

Having done that, use it as the primary source of truth, binding the
outgoing socket based on the information in there.  This allows the
possibility of more complex rules for what outbound address and/or port
we use in future.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
David Gibson 2024-07-18 15:26:31 +10:00 committed by Stefano Brivio
parent 52d45f1737
commit e2ea10e246

89
tcp.c
View file

@ -1581,46 +1581,48 @@ static uint16_t tcp_conn_tap_mss(const struct tcp_tap_conn *conn,
/** /**
* tcp_bind_outbound() - Bind socket to outbound address and interface if given * tcp_bind_outbound() - Bind socket to outbound address and interface if given
* @c: Execution context * @c: Execution context
* @conn: Connection entry for socket to bind
* @s: Outbound TCP socket * @s: Outbound TCP socket
* @af: Address family
*/ */
static void tcp_bind_outbound(const struct ctx *c, int s, sa_family_t af) static void tcp_bind_outbound(const struct ctx *c,
const struct tcp_tap_conn *conn, int s)
{ {
if (af == AF_INET) { const struct flowside *tgt = &conn->f.side[TGTSIDE];
if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out)) { union sockaddr_inany bind_sa;
struct sockaddr_in addr4 = { socklen_t sl;
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = c->ip4.addr_out,
};
if (bind(s, (struct sockaddr *)&addr4, sizeof(addr4)))
debug_perror("IPv4 TCP socket address bind"); pif_sockaddr(c, &bind_sa, &sl, PIF_HOST, &tgt->faddr, tgt->fport);
if (!inany_is_unspecified(&tgt->faddr) || tgt->fport) {
if (bind(s, &bind_sa.sa, sl)) {
char sstr[INANY_ADDRSTRLEN];
flow_dbg(conn,
"Can't bind TCP outbound socket to %s:%hu: %s",
inany_ntop(&tgt->faddr, sstr, sizeof(sstr)),
tgt->fport, strerror(errno));
}
} }
if (bind_sa.sa_family == AF_INET) {
if (*c->ip4.ifname_out) { if (*c->ip4.ifname_out) {
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
c->ip4.ifname_out, c->ip4.ifname_out,
strlen(c->ip4.ifname_out))) strlen(c->ip4.ifname_out))) {
debug_perror("IPv4 TCP socket interface bind"); flow_dbg(conn, "Can't bind IPv4 TCP socket to"
" interface %s: %s", c->ip4.ifname_out,
strerror(errno));
} }
} else if (af == AF_INET6) {
if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out)) {
struct sockaddr_in6 addr6 = {
.sin6_family = AF_INET6,
.sin6_port = 0,
.sin6_addr = c->ip6.addr_out,
};
if (bind(s, (struct sockaddr *)&addr6, sizeof(addr6)))
debug_perror("IPv6 TCP socket address bind");
} }
} else if (bind_sa.sa_family == AF_INET6) {
if (*c->ip6.ifname_out) { if (*c->ip6.ifname_out) {
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
c->ip6.ifname_out, c->ip6.ifname_out,
strlen(c->ip6.ifname_out))) strlen(c->ip6.ifname_out))) {
debug_perror("IPv6 TCP socket interface bind"); flow_dbg(conn, "Can't bind IPv6 TCP socket to"
" interface %s: %s", c->ip6.ifname_out,
strerror(errno));
}
} }
} }
} }
@ -1643,9 +1645,9 @@ static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
{ {
in_port_t srcport = ntohs(th->source); in_port_t srcport = ntohs(th->source);
in_port_t dstport = ntohs(th->dest); in_port_t dstport = ntohs(th->dest);
union inany_addr srcaddr, dstaddr; /* FIXME: Avoid bulky temporaries */
const struct flowside *ini, *tgt; const struct flowside *ini, *tgt;
struct tcp_tap_conn *conn; struct tcp_tap_conn *conn;
union inany_addr dstaddr; /* FIXME: Avoid bulky temporary */
union sockaddr_inany sa; union sockaddr_inany sa;
union flow *flow; union flow *flow;
int s = -1, mss; int s = -1, mss;
@ -1666,9 +1668,24 @@ static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
} }
/* FIXME: Record outbound source address when known */ if (inany_is_linklocal6(&dstaddr)) {
srcaddr.a6 = c->ip6.addr_ll;
} else if (inany_is_loopback(&dstaddr)) {
srcaddr = dstaddr;
} else if (inany_v4(&dstaddr)) {
if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out))
srcaddr = inany_from_v4(c->ip4.addr_out);
else
srcaddr = inany_any4;
} else {
if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out))
srcaddr.a6 = c->ip6.addr_out;
else
srcaddr = inany_any6;
}
tgt = flow_target_af(flow, PIF_HOST, AF_INET6, tgt = flow_target_af(flow, PIF_HOST, AF_INET6,
NULL, 0, /* Kernel decides source address */ &srcaddr, 0, /* Kernel decides source port */
&dstaddr, dstport); &dstaddr, dstport);
conn = FLOW_SET_TYPE(flow, FLOW_TCP, tcp); conn = FLOW_SET_TYPE(flow, FLOW_TCP, tcp);
@ -1731,18 +1748,6 @@ static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
goto cancel; goto cancel;
} }
if (inany_is_linklocal6(&tgt->eaddr)) {
struct sockaddr_in6 addr6_ll = {
.sin6_family = AF_INET6,
.sin6_addr = c->ip6.addr_ll,
.sin6_scope_id = c->ifi6,
};
if (bind(s, (struct sockaddr *)&addr6_ll, sizeof(addr6_ll)))
goto cancel;
} else if (!inany_is_loopback(&tgt->eaddr)) {
tcp_bind_outbound(c, s, af);
}
conn->sock = s; conn->sock = s;
conn->timer = -1; conn->timer = -1;
conn_event(c, conn, TAP_SYN_RCVD); conn_event(c, conn, TAP_SYN_RCVD);
@ -1771,6 +1776,8 @@ static void tcp_conn_from_tap(struct ctx *c, sa_family_t af,
tcp_hash_insert(c, conn); tcp_hash_insert(c, conn);
tcp_bind_outbound(c, conn, s);
if (connect(s, &sa.sa, sl)) { if (connect(s, &sa.sa, sl)) {
if (errno != EINPROGRESS) { if (errno != EINPROGRESS) {
tcp_rst(c, conn); tcp_rst(c, conn);