Browse Source

support hardware address with dhcpv6 and bsd socket api

IPv4 DHCP headers contain the MAC address of the client, but this is not
the case for DHCPv6. Depending on the client configuration and relays, the MAC
address can be extracted from the DUID or relay options, but in some
setups it was not possible to set a "fixed-address6" for a "hardware ethernet".

The socket api used by default on most Linux installations does not give
access to the MAC address the packet was received from, so setting the haddr
field of the packet is not easily possible.

This implementation uses libnetlink from the iproute2 project to obtain a
mapping between IPv6 addresses and MAC addresses that is maintained by the
kernel.

NOTE: This implementation is not production ready! Before upstreaming
the dependency on the Linux kernel headers and libmnl should be made
optional and properly integrated into the build systemwith automake and
autoconf. Someone who is more experienced should have a look at the code
to identify possible security issues.
master
Milan 4 months ago
parent
commit
783bad277f
7 changed files with 1818 additions and 7 deletions
  1. +5
    -2
      common/Makefile.am
  2. +5
    -2
      common/Makefile.am.in
  3. +1442
    -0
      common/libnetlink.c
  4. +2
    -2
      includes/Makefile.am
  5. +287
    -0
      includes/libnetlink.h
  6. +3
    -1
      server/Makefile.am
  7. +74
    -0
      server/mdb6.c

+ 5
- 2
common/Makefile.am View File

@@ -1,15 +1,18 @@
AM_CPPFLAGS = -I$(top_srcdir) -DLOCALSTATEDIR='"@localstatedir@"'
AM_CFLAGS = $(LDAP_CFLAGS)
AM_CFLAGS = $(LDAP_CFLAGS) -DHAVE_LIBMNL

lib_LIBRARIES = libdhcp.a
lib_LIBRARIES = libdhcp.a libnetlink.a
libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c dhcp4o6.c \
discover.c dispatch.c dlpi.c dns.c ethernet.c execute.c \
fddi.c icmp.c inet.c lpf.c memory.c nit.c ns_name.c \
options.c packet.c parse.c print.c raw.c resolv.c \
socket.c tables.c tr.c tree.c upf.c
libnetlink_a_SOURCES = libnetlink.c
man_MANS = dhcp-eval.5 dhcp-options.5
EXTRA_DIST = $(man_MANS)

AM_LDFLAGS = -lmnl

# We want to build this directory first, before descending into tests subdir.
# The reason is that ideally the tests should link existing objects from this
# directory. That eliminates any discrepancies between tested code and


+ 5
- 2
common/Makefile.am.in View File

@@ -1,15 +1,18 @@
AM_CPPFLAGS = -I$(top_srcdir) -DLOCALSTATEDIR='"@Q@localstatedir@Q@"'
AM_CFLAGS = $(LDAP_CFLAGS)
AM_CFLAGS = $(LDAP_CFLAGS) -DHAVE_LIBMNL

lib_@DHLIBS@ = libdhcp.@A@
lib_@DHLIBS@ = libdhcp.@A@ libnetlink.@A@
libdhcp_@A@_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c dhcp4o6.c \
discover.c dispatch.c dlpi.c dns.c ethernet.c execute.c \
fddi.c icmp.c inet.c lpf.c memory.c nit.c ns_name.c \
options.c packet.c parse.c print.c raw.c resolv.c \
socket.c tables.c tr.c tree.c upf.c
libnetlink_@A@_SOURCES = libnetlink.c
man_MANS = dhcp-eval.5 dhcp-options.5
EXTRA_DIST = $(man_MANS)

AM_LDFLAGS = -lmnl

# We want to build this directory first, before descending into tests subdir.
# The reason is that ideally the tests should link existing objects from this
# directory. That eliminates any discrepancies between tested code and


+ 1442
- 0
common/libnetlink.c
File diff suppressed because it is too large
View File


+ 2
- 2
includes/Makefile.am View File

@@ -7,5 +7,5 @@ EXTRA_DIST = cdefs.h ctrace.h dhcp.h dhcp6.h dhcpd.h dhctoken.h failover.h \
t_api.h \
ldap_casa.h ldap_krb_helper.h \
arpa/nameser.h arpa/nameser_compat.h \
netinet/if_ether.h netinet/ip.h netinet/ip_icmp.h netinet/udp.h
netinet/if_ether.h netinet/ip.h netinet/ip_icmp.h netinet/udp.h \
libnetlink.h

+ 287
- 0
includes/libnetlink.h View File

@@ -0,0 +1,287 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LIBNETLINK_H__
#define __LIBNETLINK_H__ 1

#include <stdio.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include <linux/if_addr.h>
#include <linux/neighbour.h>
#include <linux/netconf.h>
#include <arpa/inet.h>

struct rtnl_handle {
int fd;
struct sockaddr_nl local;
struct sockaddr_nl peer;
__u32 seq;
__u32 dump;
int proto;
FILE *dump_fp;
#define RTNL_HANDLE_F_LISTEN_ALL_NSID 0x01
#define RTNL_HANDLE_F_SUPPRESS_NLERR 0x02
#define RTNL_HANDLE_F_STRICT_CHK 0x04
int flags;
};

struct nlmsg_list {
struct nlmsg_list *next;
struct nlmsghdr h;
};

struct nlmsg_chain {
struct nlmsg_list *head;
struct nlmsg_list *tail;
};

extern int rcvbuf;

int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
__attribute__((warn_unused_result));

int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
int protocol)
__attribute__((warn_unused_result));
int rtnl_add_nl_group(struct rtnl_handle *rth, unsigned int group)
__attribute__((warn_unused_result));
void rtnl_close(struct rtnl_handle *rth);
void rtnl_set_strict_dump(struct rtnl_handle *rth);

typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen);

int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));
int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
__attribute__((warn_unused_result));
int rtnl_routedump_req(struct rtnl_handle *rth, int family,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));
int rtnl_ruledump_req(struct rtnl_handle *rth, int family)
__attribute__((warn_unused_result));
int rtnl_neighdump_req(struct rtnl_handle *rth, int family,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));
int rtnl_neightbldump_req(struct rtnl_handle *rth, int family)
__attribute__((warn_unused_result));
int rtnl_mdbdump_req(struct rtnl_handle *rth, int family)
__attribute__((warn_unused_result));
int rtnl_netconfdump_req(struct rtnl_handle *rth, int family)
__attribute__((warn_unused_result));

int rtnl_linkdump_req(struct rtnl_handle *rth, int fam)
__attribute__((warn_unused_result));
int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask)
__attribute__((warn_unused_result));

int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int fam,
req_filter_fn_t fn)
__attribute__((warn_unused_result));
int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));
int rtnl_nsiddump_req_filter_fn(struct rtnl_handle *rth, int family,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));
int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask)
__attribute__((warn_unused_result));
int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req,
int len)
__attribute__((warn_unused_result));
int rtnl_dump_request_n(struct rtnl_handle *rth, struct nlmsghdr *n)
__attribute__((warn_unused_result));

int rtnl_nexthopdump_req(struct rtnl_handle *rth, int family,
req_filter_fn_t filter_fn)
__attribute__((warn_unused_result));

struct rtnl_ctrl_data {
int nsid;
};

typedef int (*rtnl_filter_t)(struct nlmsghdr *n, void *);

typedef int (*rtnl_listen_filter_t)(struct rtnl_ctrl_data *,
struct nlmsghdr *n, void *);

typedef int (*nl_ext_ack_fn_t)(const char *errmsg, uint32_t off,
const struct nlmsghdr *inner_nlh);

struct rtnl_dump_filter_arg {
rtnl_filter_t filter;
void *arg1;
__u16 nc_flags;
};

int rtnl_dump_filter_nc(struct rtnl_handle *rth,
rtnl_filter_t filter,
void *arg, __u16 nc_flags);
#define rtnl_dump_filter(rth, filter, arg) \
rtnl_dump_filter_nc(rth, filter, arg, 0)
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr **answer)
__attribute__((warn_unused_result));
int rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iovec, size_t iovlen,
struct nlmsghdr **answer)
__attribute__((warn_unused_result));
int rtnl_talk_suppress_rtnl_errmsg(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr **answer)
__attribute__((warn_unused_result));
int rtnl_send(struct rtnl_handle *rth, const void *buf, int)
__attribute__((warn_unused_result));
int rtnl_send_check(struct rtnl_handle *rth, const void *buf, int)
__attribute__((warn_unused_result));
int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn);
int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error);

int addattr(struct nlmsghdr *n, int maxlen, int type);
int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data);
int addattr16(struct nlmsghdr *n, int maxlen, int type, __u16 data);
int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data);
int addattr64(struct nlmsghdr *n, int maxlen, int type, __u64 data);
int addattrstrz(struct nlmsghdr *n, int maxlen, int type, const char *data);

int addattr_l(struct nlmsghdr *n, int maxlen, int type,
const void *data, int alen);
int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len);
struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type);
int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest);
struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type,
const void *data, int len);
int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *nest);
int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data);
int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data);
int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data);
int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data);
int rta_addattr_l(struct rtattr *rta, int maxlen, int type,
const void *data, int alen);

int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len);
int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
int len, unsigned short flags);
struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len);
int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len);

struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type);
int rta_nest_end(struct rtattr *rta, struct rtattr *nest);

#define RTA_TAIL(rta) \
((struct rtattr *) (((void *) (rta)) + \
RTA_ALIGN((rta)->rta_len)))

#define parse_rtattr_nested(tb, max, rta) \
(parse_rtattr_flags((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta), \
NLA_F_NESTED))

#define parse_rtattr_one_nested(type, rta) \
(parse_rtattr_one(type, RTA_DATA(rta), RTA_PAYLOAD(rta)))

#define parse_rtattr_nested_compat(tb, max, rta, data, len) \
({ data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \
__parse_rtattr_nested_compat(tb, max, rta, len); })

static inline __u8 rta_getattr_u8(const struct rtattr *rta)
{
return *(__u8 *)RTA_DATA(rta);
}
static inline __u16 rta_getattr_u16(const struct rtattr *rta)
{
return *(__u16 *)RTA_DATA(rta);
}
static inline __be16 rta_getattr_be16(const struct rtattr *rta)
{
return ntohs(rta_getattr_u16(rta));
}
static inline __u32 rta_getattr_u32(const struct rtattr *rta)
{
return *(__u32 *)RTA_DATA(rta);
}
static inline __be32 rta_getattr_be32(const struct rtattr *rta)
{
return ntohl(rta_getattr_u32(rta));
}
static inline __u64 rta_getattr_u64(const struct rtattr *rta)
{
__u64 tmp;

memcpy(&tmp, RTA_DATA(rta), sizeof(__u64));
return tmp;
}
static inline __s32 rta_getattr_s32(const struct rtattr *rta)
{
return *(__s32 *)RTA_DATA(rta);
}
static inline __s64 rta_getattr_s64(const struct rtattr *rta)
{
__s64 tmp;

memcpy(&tmp, RTA_DATA(rta), sizeof(tmp));
return tmp;
}
static inline const char *rta_getattr_str(const struct rtattr *rta)
{
return (const char *)RTA_DATA(rta);
}

int rtnl_listen_all_nsid(struct rtnl_handle *);
int rtnl_listen(struct rtnl_handle *, rtnl_listen_filter_t handler,
void *jarg);
int rtnl_from_file(FILE *, rtnl_listen_filter_t handler,
void *jarg);

#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))

#ifndef IFA_RTA
#define IFA_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
#endif
#ifndef IFA_PAYLOAD
#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg))
#endif

#ifndef IFLA_RTA
#define IFLA_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
#endif
#ifndef IFLA_PAYLOAD
#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg))
#endif

#ifndef NDA_RTA
#define NDA_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
#endif
#ifndef NDA_PAYLOAD
#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ndmsg))
#endif

#ifndef NDTA_RTA
#define NDTA_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg))))
#endif
#ifndef NDTA_PAYLOAD
#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ndtmsg))
#endif

#ifndef NETNS_RTA
#define NETNS_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))))
#endif
#ifndef NETNS_PAYLOAD
#define NETNS_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct rtgenmsg))
#endif

#ifndef IFLA_STATS_RTA
#define IFLA_STATS_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct if_stats_msg))))
#endif

/* User defined nlmsg_type which is used mostly for logging netlink
* messages from dump file */
#define NLMSG_TSTAMP 15

#endif /* __LIBNETLINK_H__ */

+ 3
- 1
server/Makefile.am View File

@@ -14,12 +14,14 @@ dhcpd_SOURCES = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \

dhcpd_CFLAGS = $(LDAP_CFLAGS)
dhcpd_LDADD = ../common/libdhcp.@A@ ../omapip/libomapi.@A@ \
../dhcpctl/libdhcpctl.@A@ \
../dhcpctl/libdhcpctl.@A@ ../common/libnetlink.@A@ \
$(BINDLIBIRSDIR)/libirs.@A@ \
$(BINDLIBDNSDIR)/libdns.@A@ \
$(BINDLIBISCCFGDIR)/libisccfg.@A@ \
$(BINDLIBISCDIR)/libisc.@A@ $(LDAP_LIBS)

AM_LDFLAGS = -lmnl

man_MANS = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
EXTRA_DIST = $(man_MANS)


+ 74
- 0
server/mdb6.c View File

@@ -176,6 +176,7 @@
#include "omapip/omapip.h"
#include "omapip/hash.h"
#include <isc/md5.h>
#include "libnetlink.h"

HASH_FUNCTIONS(ia, unsigned char *, struct ia_xx, ia_hash_t,
ia_reference, ia_dereference, do_string_hash)
@@ -2935,6 +2936,78 @@ int find_hosts_by_haddr6(struct host_decl **hp,
return (found);
}

struct find_hosts_by_netlink_state {
struct in6_addr client_addr;
unsigned char *ll_addr;
int found;
};

int find_hosts_by_netlink_callback(struct nlmsghdr *n, void *arg) {
struct find_hosts_by_netlink_state *state = arg;

if (state->found) return 0;

struct rtattr *tb[NDA_MAX+1];
struct ndmsg *r = NLMSG_DATA(n);
parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
if (!tb[NDA_DST]) return 0;
if (RTA_PAYLOAD(tb[NDA_DST]) != sizeof(struct in6_addr)) return 0;
if (!tb[NDA_LLADDR]) return 0;
if (RTA_PAYLOAD(tb[NDA_LLADDR]) != 6) return 0;

struct in6_addr *a = RTA_DATA(tb[NDA_DST]);
if (memcmp(state->client_addr.s6_addr, a->s6_addr, 16) != 0) return 0;

state->found = 1;
state->ll_addr = RTA_DATA(tb[NDA_LLADDR]);
return 1;
}

/*
* Find hosts by matching the "hardware ethernet" MAC address to the the MAC
* address mapped to the IPv6 address by the kernel.
* Retrieves a list of IPv6-MAC-mappings from the kernel via netlink.
* This is especially useful on systems where the socket API does not allow
* access to the hardware address the packet was received on.
*/
int find_hosts_by_netlink(struct host_decl **hp,
struct packet *packet,
struct option_state *opt_state,
const char *file, int line) {
struct rtnl_handle rth;
struct find_hosts_by_netlink_state state;
int found = 0;

memset(&state, 0, sizeof state);
if (packet->client_addr.len == 16) {
memcpy(&state.client_addr, &packet->client_addr.iabuf, 16);
} else {
log_debug("find_hosts_by_netlink: no client_addr");
return (0);
}

/*
* If client is not directly connected, we will not be able to get its
* hardware address from netlink.
*/
if (packet->dhcpv6_container_packet != NULL) return (0);

if (rtnl_open(&rth, 0) >= 0) {
if (rtnl_neighdump_req(&rth, AF_INET6, NULL) >= 0)
if (rtnl_dump_filter(&rth, find_hosts_by_netlink_callback, &state) >= 0)
found = find_hosts_by_haddr(hp, 1, state.ll_addr, 6, MDL);
else
log_debug("find_hosts_by_netlink: netlink dump terminated");
else
log_debug("find_hosts_by_netlink: netlink dump request failed");
} else {
log_debug("find_hosts_by_netlink: netlink open failed");
}
rtnl_close(&rth);

return (found);
}

/*
* find_host_by_duid_chaddr() synthesizes a DHCPv4-like 'hardware'
* parameter from a DHCPv6 supplied DUID (client-identifier option),
@@ -3016,6 +3089,7 @@ find_hosts6(struct host_decl** host, struct packet* packet,
const struct data_string* client_id, char* file, int line) {
return (find_hosts_by_uid(host, client_id->data, client_id->len, MDL)
|| find_hosts_by_haddr6(host, packet, packet->options, MDL)
|| find_hosts_by_netlink(host, packet, packet->options, MDL)
|| find_hosts_by_option(host, packet, packet->options, MDL)
|| find_hosts_by_duid_chaddr(host, client_id));
}


Loading…
Cancel
Save