summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2021-12-30 21:20:37 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2021-12-30 21:20:37 +0000
commitd242cbffa4f20c9f7472f79b3a9e47008b6fe77c (patch)
tree1459bde62515b258d3061df9740aec64be9f6f2e
parent1c8855ed10d3923a9a4fd8a89f1c95439d4c8827 (diff)
downloaddnsmasq-dhcpv6-snoop.tar.gz
Add snooping of DHCPv6 prefix delegation to the DHCP-relay function.dhcpv6-snoop
-rw-r--r--CHANGELOG2
-rw-r--r--man/dnsmasq.821
-rw-r--r--src/dhcp6-protocol.h2
-rw-r--r--src/dhcp6.c3
-rw-r--r--src/dnsmasq.c15
-rw-r--r--src/dnsmasq.h19
-rw-r--r--src/helper.c57
-rw-r--r--src/rfc3315.c69
8 files changed, 175 insertions, 13 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 4a012de..aabeb5a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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.
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 8bdcaa3..4de8969 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -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
diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h
index ebe8dfd..f1d0991 100644
--- a/src/dhcp6-protocol.h
+++ b/src/dhcp6-protocol.h
@@ -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
diff --git a/src/dhcp6.c b/src/dhcp6.c
index ae1f5c1..c061879 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -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))));
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 2fe9808..422e40f 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -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
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 05c1743..ba09bd0 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -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 */
diff --git a/src/helper.c b/src/helper.c
index 455a68c..d7c7dbd 100644
--- a/src/helper.c
+++ b/src/helper.c
@@ -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)
diff --git a/src/rfc3315.c b/src/rfc3315.c
index f54fb78..fb387a4 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -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;
}