Improve the performance of DHCP relay.

On machines with many interfaces, enumerating them
via netlink on each packet reciept is slow,
and unneccesary. All we need is the local address->interface
mapping, which can be cached in the relay structures.
This commit is contained in:
Simon Kelley
2022-02-01 00:18:44 +00:00
parent dbceeb4178
commit 7fbf1cce7b
5 changed files with 217 additions and 207 deletions

View File

@@ -20,8 +20,6 @@
struct iface_param {
struct dhcp_context *current;
struct dhcp_relay *relay;
struct in_addr relay_local;
int ind;
};
@@ -34,7 +32,7 @@ static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
static int check_listen_addrs(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index);
static int relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz);
static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
static int make_fd(int port)
@@ -307,12 +305,7 @@ void dhcp_packet(time_t now, int pxe_fd)
for (context = daemon->dhcp; context; context = context->next)
context->current = context;
for (relay = daemon->relay4; relay; relay = relay->next)
relay->current = relay;
parm.current = NULL;
parm.relay = NULL;
parm.relay_local.s_addr = 0;
parm.ind = iface_index;
if (!iface_check(AF_INET, (union all_addr *)&iface_addr, ifr.ifr_name, NULL))
@@ -335,12 +328,16 @@ void dhcp_packet(time_t now, int pxe_fd)
complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
}
if (relay_upstream4(iface_index, mess, (size_t)sz))
return;
if (!iface_enumerate(AF_INET, &parm, complete_context))
return;
/* We're relaying this request */
if (parm.relay_local.s_addr != 0 &&
relay_upstream4(parm.relay, mess, (size_t)sz, iface_index))
/* Check for a relay again after iface_enumerate/complete_context has had
chance to fill in relay->iface_index fields. This handles first time through
and any changes in interface config. */
if (relay_upstream4(iface_index, mess, (size_t)sz))
return;
/* May have configured relay, but not DHCP server */
@@ -631,13 +628,8 @@ static int complete_context(struct in_addr local, int if_index, char *label,
}
for (relay = daemon->relay4; relay; relay = relay->next)
if (if_index == param->ind && relay->local.addr4.s_addr == local.s_addr && relay->current == relay &&
(param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr))
{
relay->current = param->relay;
param->relay = relay;
param->relay_local = local;
}
if (relay->local.addr4.s_addr == local.s_addr)
relay->iface_index = if_index;
return 1;
}
@@ -1079,23 +1071,44 @@ char *host_from_dns(struct in_addr addr)
return NULL;
}
static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index)
static int relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz)
{
/* ->local is same value for all relays on ->current chain */
union all_addr from;
struct in_addr giaddr = mess->giaddr;
u8 hops = mess->hops;
struct dhcp_relay *relay;
if (mess->op != BOOTREQUEST)
return 0;
for (relay = daemon->relay4; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
break;
/* No relay config. */
if (!relay)
return 0;
for (; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
{
union mysockaddr to;
union all_addr from;
mess->hops = hops;
mess->giaddr = giaddr;
if ((mess->hops++) > 20)
continue;
/* source address == relay address */
from.addr4 = relay->local.addr4;
/* already gatewayed ? */
if (mess->giaddr.s_addr)
if (giaddr.s_addr)
{
/* if so check if by us, to stomp on loops. */
if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
return 1;
if (giaddr.s_addr == relay->local.addr4.s_addr)
continue;
}
else
{
@@ -1103,13 +1116,6 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess,
mess->giaddr.s_addr = relay->local.addr4.s_addr;
}
if ((mess->hops++) > 20)
return 1;
for (; relay; relay = relay->current)
{
union mysockaddr to;
to.sa.sa_family = AF_INET;
to.in.sin_addr = relay->server.addr4;
to.in.sin_port = htons(daemon->dhcp_server_port);
@@ -1126,7 +1132,7 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess,
ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) == -1)
{
my_syslog(MS_DHCP | LOG_ERR, _("Cannot broadcast DHCP relay via interface %s"), relay->interface);
return 1;
continue;
}
to.in.sin_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
@@ -1154,9 +1160,6 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess,
inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ);
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2);
}
/* Save this for replies */
relay->iface_index = iface_index;
}
return 1;

View File

@@ -22,8 +22,7 @@
struct iface_param {
struct dhcp_context *current;
struct dhcp_relay *relay;
struct in6_addr fallback, relay_local, ll_addr, ula_addr;
struct in6_addr fallback, ll_addr, ula_addr;
int ind, addr_match;
};
@@ -90,7 +89,6 @@ void dhcp6_init(void)
void dhcp6_packet(time_t now)
{
struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param parm;
struct cmsghdr *cmptr;
struct msghdr msg;
@@ -105,6 +103,7 @@ void dhcp6_packet(time_t now)
struct iname *tmp;
unsigned short port;
struct in6_addr dst_addr;
struct in6_addr all_servers;
memset(&dst_addr, 0, sizeof(dst_addr));
@@ -164,8 +163,6 @@ void dhcp6_packet(time_t now)
return;
parm.current = NULL;
parm.relay = NULL;
memset(&parm.relay_local, 0, IN6ADDRSZ);
parm.ind = if_index;
parm.addr_match = 0;
memset(&parm.fallback, 0, IN6ADDRSZ);
@@ -210,12 +207,24 @@ void dhcp6_packet(time_t now)
memset(&context->local6, 0, IN6ADDRSZ);
}
for (relay = daemon->relay6; relay; relay = relay->next)
relay->current = relay;
/* Ignore requests sent to the ALL_SERVERS multicast address for relay when
we're listening there for DHCPv6 server reasons. */
inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) &&
relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
return;
if (!iface_enumerate(AF_INET6, &parm, complete_context6))
return;
/* Check for a relay again after iface_enumerate/complete_context has had
chance to fill in relay->iface_index fields. This handles first time through
and any changes in interface config. */
if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) &&
relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
return;
if (daemon->if_names || daemon->if_addrs)
{
@@ -227,19 +236,6 @@ void dhcp6_packet(time_t now)
return;
}
if (parm.relay)
{
/* Ignore requests sent to the ALL_SERVERS multicast address for relay when
we're listening there for DHCPv6 server reasons. */
struct in6_addr all_servers;
inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id, now);
return;
}
/* May have configured relay, but not DHCP server */
if (!daemon->doing_dhcp6)
return;
@@ -326,6 +322,7 @@ static int complete_context6(struct in6_addr *local, int prefix,
struct dhcp_relay *relay;
struct iface_param *param = vparam;
struct iname *tmp;
int match = !daemon->if_addrs;
(void)scope; /* warning */
@@ -347,7 +344,7 @@ static int complete_context6(struct in6_addr *local, int prefix,
for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
if (tmp->addr.sa.sa_family == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
param->addr_match = 1;
match = param->addr_match = 1;
/* Determine a globally address on the arrival interface, even
if we have no matching dhcp-context, because we're only
@@ -420,14 +417,10 @@ static int complete_context6(struct in6_addr *local, int prefix,
}
}
if (match)
for (relay = daemon->relay6; relay; relay = relay->next)
if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay &&
(IN6_IS_ADDR_UNSPECIFIED(&param->relay_local) || IN6_ARE_ADDR_EQUAL(local, &param->relay_local)))
{
relay->current = param->relay;
param->relay = relay;
param->relay_local = *local;
}
if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6))
relay->iface_index = if_index;
return 1;
}

View File

@@ -1089,7 +1089,7 @@ struct dhcp_relay {
struct snoop_record *next;
} *snoop_records;
#endif
struct dhcp_relay *current, *next;
struct dhcp_relay *next;
};
extern struct daemon {
@@ -1682,7 +1682,7 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
size_t sz, struct in6_addr *client_addr, time_t now);
void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address,
int relay_upstream6(int iface_index, ssize_t sz, struct in6_addr *peer_address,
u32 scope_id, time_t now);
int relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);

View File

@@ -1743,6 +1743,8 @@ int reload_servers(char *fname)
/* Called when addresses are added or deleted from an interface */
void newaddress(time_t now)
{
struct dhcp_relay *relay;
(void)now;
if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) ||
@@ -1752,6 +1754,12 @@ void newaddress(time_t now)
if (option_bool(OPT_CLEVERBIND))
create_bound_listeners(0);
#ifdef HAVE_DHCP
/* clear cache of subnet->relay index */
for (relay = daemon->relay4; relay; relay = relay->next)
relay->iface_index = 0;
#endif
#ifdef HAVE_DHCP6
if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
join_multicast(0);
@@ -1761,5 +1769,8 @@ void newaddress(time_t now)
if (daemon->doing_dhcp6)
lease_find_interfaces(now);
for (relay = daemon->relay6; relay; relay = relay->next)
relay->iface_index = 0;
#endif
}

View File

@@ -2100,45 +2100,43 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
return ret;
}
void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
int relay_upstream6(int iface_index, ssize_t sz,
struct in6_addr *peer_address, u32 scope_id, time_t now)
{
/* ->local is same value for all relays on ->current chain */
union all_addr from;
unsigned char *header;
unsigned char *inbuff = daemon->dhcp_packet.iov_base;
int msg_type = *inbuff;
int hopcount;
int hopcount, o;
struct in6_addr multicast;
unsigned int maclen, mactype;
unsigned char mac[DHCP_CHADDR_MAX];
struct dhcp_relay *relay;
for (relay = daemon->relay6; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
break;
/* No relay config. */
if (!relay)
return 0;
inet_pton(AF_INET6, ALL_SERVERS, &multicast);
get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now);
/* source address == relay address */
from.addr6 = relay->local.addr6;
/* Get hop count from nested relayed message */
if (msg_type == DHCP6RELAYFORW)
hopcount = *((unsigned char *)inbuff+1) + 1;
else
hopcount = 0;
/* RFC 3315 HOP_COUNT_LIMIT */
if (hopcount > 32)
return;
reset_counter();
if ((header = put_opt6(NULL, 34)))
{
int o;
/* RFC 3315 HOP_COUNT_LIMIT */
if (hopcount > 32 || !(header = put_opt6(NULL, 34)))
return 1;
header[0] = DHCP6RELAYFORW;
header[1] = hopcount;
memcpy(&header[2], &relay->local.addr6, IN6ADDRSZ);
memcpy(&header[18], peer_address, IN6ADDRSZ);
/* RFC-6939 */
@@ -2154,9 +2152,15 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
put_opt6(inbuff, sz);
end_opt6(o);
for (; relay; relay = relay->current)
for (; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
{
union mysockaddr to;
union all_addr from;
/* source address == relay address */
from.addr6 = relay->local.addr6;
memcpy(&header[2], &relay->local.addr6, IN6ADDRSZ);
to.sa.sa_family = AF_INET6;
to.in6.sin6_addr = relay->server.addr6;
@@ -2172,7 +2176,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_iface, sizeof(multicast_iface)) == -1)
{
my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast DHCP relay via interface %s"), relay->interface);
return;
continue;
}
}
@@ -2200,10 +2204,9 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->namebuff);
}
/* Save this for replies */
relay->iface_index = scope_id;
}
}
return 1;
}
int relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)