Add snooping of DHCPv6 prefix delegation to the DHCP-relay function.

This commit is contained in:
Simon Kelley
2021-12-30 21:20:37 +00:00
parent 1c8855ed10
commit d242cbffa4
8 changed files with 177 additions and 15 deletions

View File

@@ -19,6 +19,8 @@ version 2.87
now supported for both IPv4 and IPv6 and the configuration
syntax made easier (but backwards compatible).
Add snooping of IPv6 prefix-delegations to the DHCP-relay system.
version 2.86
Handle DHCPREBIND requests in the DHCPv6 server code.

View File

@@ -1356,6 +1356,11 @@ supported: the relay function will take precedence.
Both DHCPv4 and DHCPv6 relay is supported. It's not possible to relay
DHCPv4 to a DHCPv6 server or vice-versa.
The DHCP relay function for IPv6 includes the ability to snoop
prefix-delegation from relayed DHCP transactions. See
.B --dhcp-script
for details.
.TP
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,]<vendor-class>
Map from a vendor-class string to a tag. Most DHCP clients provide a
@@ -1766,15 +1771,25 @@ receives a HUP signal, the script will be invoked for existing leases
with an "old" event.
There are four further actions which may appear as the first argument
to the script, "init", "arp-add", "arp-del" and "tftp". More may be added in the future, so
There are five further actions which may appear as the first argument
to the script, "init", "arp-add", "arp-del", "relay-snoop" and "tftp".
More may be added in the future, so
scripts should be written to ignore unknown actions. "init" is
described below in
.B --leasefile-ro
The "tftp" action is invoked when a TFTP file transfer completes: the
arguments are the file size in bytes, the address to which the file
was sent, and the complete pathname of the file.
The "relay-snoop" action is invoked when dnsmasq is configured as a DHCP
relay for DHCPv6 and it relays a prefx delegation to a client. The arguments
are the name of the interface where the client is conected, its (link-local)
address on that interface and the delegated prefix. This information is
sufficient to install routes to the delegated prefix of a router. See
.B --dhcp-relay
for more details on configuring DHCP relay.
The "arp-add" and "arp-del" actions are only called if enabled with
.B --script-arp
They are are supplied with a MAC address and IP address as arguments. "arp-add" indicates

View File

@@ -55,6 +55,8 @@
#define OPTION6_RECONF_ACCEPT 20
#define OPTION6_DNS_SERVER 23
#define OPTION6_DOMAIN_SEARCH 24
#define OPTION6_IA_PD 25
#define OPTION6_IAPREFIX 26
#define OPTION6_REFRESH_TIME 32
#define OPTION6_REMOTE_ID 37
#define OPTION6_SUBSCRIBER_ID 38

View File

@@ -135,9 +135,8 @@ void dhcp6_packet(time_t now)
if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
return;
if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0)
if (relay_reply6(&from, sz, ifr.ifr_name))
{
from.sin6_port = htons(port);
while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base,
save_counter(-1), 0, (struct sockaddr *)&from,
sizeof(from))));

View File

@@ -734,7 +734,11 @@ int main (int argc, char **argv)
/* if we are to run scripts, we need to fork a helper before dropping root. */
daemon->helperfd = -1;
#ifdef HAVE_SCRIPT
if ((daemon->dhcp || daemon->dhcp6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) &&
if ((daemon->dhcp ||
daemon->dhcp6 ||
daemon->relay6 ||
option_bool(OPT_TFTP) ||
option_bool(OPT_SCRIPT_ARP)) &&
(daemon->lease_change_command || daemon->luascript))
daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd);
#endif
@@ -1139,6 +1143,10 @@ int main (int argc, char **argv)
while (helper_buf_empty() && do_tftp_script_run());
# endif
# ifdef HAVE_DHCP6
while (helper_buf_empty() && do_snoop_script_run());
# endif
if (!helper_buf_empty())
poll_listen(daemon->helperfd, POLLOUT);
#else
@@ -1153,6 +1161,11 @@ int main (int argc, char **argv)
while (do_tftp_script_run());
# endif
# ifdef HAVE_DHCP6
while (helper_buf_empty() && do_snoop_script_run());
# endif
#endif

View File

@@ -778,6 +778,7 @@ struct frec {
#define ACTION_TFTP 5
#define ACTION_ARP 6
#define ACTION_ARP_DEL 7
#define ACTION_RELAY_SNOOP 8
#define LEASE_NEW 1 /* newly created */
#define LEASE_CHANGED 2 /* modified */
@@ -1076,6 +1077,13 @@ struct dhcp_relay {
union all_addr local, server;
char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
int iface_index; /* working - interface in which requests arrived, for return */
#ifdef HAVE_SCRIPT
struct snoop_record {
struct in6_addr client, prefix;
int prefix_len;
struct snoop_record *next;
} *snoop_records;
#endif
struct dhcp_relay *current, *next;
};
@@ -1227,13 +1235,18 @@ extern struct daemon {
unsigned char *duid;
struct iovec outpacket;
int dhcp6fd, icmp6fd;
# ifdef HAVE_SCRIPT
struct snoop_record *free_snoops;
# endif
#endif
/* DBus stuff */
/* void * here to avoid depending on dbus headers outside dbus.c */
void *dbus;
#ifdef HAVE_DBUS
struct watch *watches;
#endif
/* UBus stuff */
#ifdef HAVE_UBUS
/* void * here to avoid depending on ubus headers outside ubus.c */
@@ -1619,6 +1632,9 @@ void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer);
void queue_arp(int action, unsigned char *mac, int maclen,
int family, union all_addr *addr);
int helper_buf_empty(void);
#ifdef HAVE_DHCP6
void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *prefix, int prefix_len);
#endif
#endif
/* tftp.c */
@@ -1664,7 +1680,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address,
u32 scope_id, time_t now);
unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
int relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
int do_snoop_script_run(void);
#endif
/* dhcp-common.c */

View File

@@ -233,8 +233,13 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
is6 = (data.flags != AF_INET);
data.action = ACTION_ARP;
}
else
continue;
else if (data.action == ACTION_RELAY_SNOOP)
{
is6 = 1;
action_str = "relay-snoop";
}
else
continue;
/* stringify MAC into dhcp_buff */
p = daemon->dhcp_buff;
@@ -286,7 +291,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
char *dot;
hostname = (char *)buf;
hostname[data.hostname_len - 1] = 0;
if (data.action != ACTION_TFTP)
if (data.action != ACTION_TFTP && data.action != ACTION_RELAY_SNOOP)
{
if (!legal_hostname(hostname))
hostname = NULL;
@@ -332,6 +337,24 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
}
}
else if (data.action == ACTION_RELAY_SNOOP)
{
lua_getglobal(lua, "snoop");
if (lua_type(lua, -1) != LUA_TFUNCTION)
lua_pop(lua, 1); /* tftp function optional */
else
{
lua_pushstring(lua, action_str); /* arg1 - action */
lua_newtable(lua); /* arg2 - data table */
lua_pushstring(lua, daemon->addrbuff);
lua_setfield(lua, -2, "client_address");
lua_pushstring(lua, hostname);
lua_setfield(lua, -2, "prefix");
lua_pushstring(lua, data.interface);
lua_setfield(lua, -2, "client_interface");
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
}
}
else if (data.action == ACTION_ARP)
{
lua_getglobal(lua, "arp");
@@ -553,7 +576,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
close(pipeout[1]);
}
if (data.action != ACTION_TFTP && data.action != ACTION_ARP)
if (data.action != ACTION_TFTP && data.action != ACTION_ARP && data.action != ACTION_RELAY_SNOOP)
{
#ifdef HAVE_DHCP6
my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err);
@@ -640,6 +663,9 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
close(pipefd[0]);
if (data.action == ACTION_RELAY_SNOOP)
strcpy(daemon->packet, data.interface);
p = strrchr(daemon->lease_change_command, '/');
if (err == 0)
{
@@ -810,6 +836,29 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n
bytes_in_buf = p - (unsigned char *)buf;
}
#ifdef HAVE_DHCP6
void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *prefix, int prefix_len)
{
/* no script */
if (daemon->helperfd == -1)
return;
inet_ntop(AF_INET6, prefix, daemon->addrbuff, ADDRSTRLEN);
/* 5 for /nnn and zero on the end of the prefix. */
buff_alloc(sizeof(struct script_data) + ADDRSTRLEN + 5);
memset(buf, 0, sizeof(struct script_data));
buf->action = ACTION_RELAY_SNOOP;
buf->addr6 = *client;
buf->hostname_len = sprintf((char *)(buf+1), "%s/%u", daemon->addrbuff, prefix_len) + 1;
indextoname(daemon->dhcp6fd, if_index, buf->interface);
bytes_in_buf = sizeof(struct script_data) + buf->hostname_len;
}
#endif
#ifdef HAVE_TFTP
/* This nastily re-uses DHCP-fields for TFTP stuff */
void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer)

View File

@@ -2194,7 +2194,7 @@ void relay_upstream6(struct dhcp_relay *relay, ssize_t sz,
}
}
unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
int relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
{
struct dhcp_relay *relay;
struct in6_addr link;
@@ -2226,10 +2226,75 @@ unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival
put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ);
peer->sin6_scope_id = relay->iface_index;
return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
}
}
if (encap_type == DHCP6RELAYREPL)
{
peer->sin6_port = ntohs(DHCPV6_SERVER_PORT);
return 1;
}
peer->sin6_port = ntohs(DHCPV6_CLIENT_PORT);
#ifdef HAVE_SCRIPT
if (daemon->lease_change_command && encap_type == DHCP6REPLY)
{
/* decapsulate relayed message */
opts = opt6_ptr(opt, 4);
end = opt6_ptr(opt, opt6_len(opt));
for (opt = opts; opt; opt = opt6_next(opt, end))
if (opt6_type(opt) == OPTION6_IA_PD && opt6_len(opt) > 12)
{
void *ia_opts = opt6_ptr(opt, 12);
void *ia_end = opt6_ptr(opt, opt6_len(opt));
void *ia_opt;
for (ia_opt = ia_opts; ia_opt; ia_opt = opt6_next(ia_opt, ia_end))
/* valid lifetime must not be zero. */
if (opt6_type(ia_opt) == OPTION6_IAPREFIX && opt6_len(ia_opt) >= 25 && opt6_uint(ia_opt, 4, 4) != 0)
{
if (daemon->free_snoops ||
(daemon->free_snoops = whine_malloc(sizeof(struct snoop_record))))
{
struct snoop_record *snoop = daemon->free_snoops;
daemon->free_snoops = snoop->next;
snoop->client = peer->sin6_addr;
snoop->prefix_len = opt6_uint(ia_opt, 8, 1);
memcpy(&snoop->prefix, opt6_ptr(ia_opt, 9), IN6ADDRSZ);
snoop->next = relay->snoop_records;
relay->snoop_records = snoop;
}
}
}
}
#endif
return 1;
}
}
return 0;
}
int do_snoop_script_run(void)
{
#ifdef HAVE_SCRIPT
struct dhcp_relay *relay;
struct snoop_record *snoop;
for (relay = daemon->relay6; relay; relay = relay->next)
if ((snoop = relay->snoop_records))
{
relay->snoop_records = snoop->next;
snoop->next = daemon->free_snoops;
daemon->free_snoops = snoop;
queue_relay_snoop(&snoop->client, relay->iface_index, &snoop->prefix, snoop->prefix_len);
return 1;
}
#endif
return 0;
}