#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
+#include <syslog.h>
+#include <alloca.h>
#include <arpa/inet.h>
#include <net/if.h>
static struct nl_sock *rtnl_socket = NULL;
static int urandom_fd = -1;
-
static void sighandler(_unused int signal)
{
uloop_end();
"== %s Usage ==\n\n"
" -h, --help Print this help\n"
" -l level Specify log level 0..7 (default %d)\n",
- app, LOG_WARNING
+ app, config.log_level
);
}
{
openlog("odhcpd", LOG_PERROR | LOG_PID, LOG_DAEMON);
int opt;
- int log_level = LOG_INFO;
+
while ((opt = getopt(argc, argv, "hl:")) != -1) {
switch (opt) {
case 'h':
print_usage(argv[0]);
return 0;
case 'l':
- log_level = atoi(optarg);
- fprintf(stderr, "Log level set to %d\n", log_level);
+ config.log_level = (atoi(optarg) & LOG_PRIMASK);
+ fprintf(stderr, "Log level set to %d\n", config.log_level);
break;
}
}
- setlogmask(LOG_UPTO(log_level));
+ setlogmask(LOG_UPTO(config.log_level));
uloop_init();
if (getuid() != 0) {
struct addr_info {
int ifindex;
- struct odhcpd_ipaddr *addrs;
- size_t addrs_sz;
+ int af;
+ struct odhcpd_ipaddr **addrs;
int pending;
ssize_t ret;
};
static int cb_valid_handler(struct nl_msg *msg, void *arg)
{
struct addr_info *ctxt = (struct addr_info *)arg;
+ struct odhcpd_ipaddr *addrs = *(ctxt->addrs);
struct nlmsghdr *hdr = nlmsg_hdr(msg);
struct ifaddrmsg *ifa;
- struct nlattr *nla[__IFA_MAX];
+ struct nlattr *nla[__IFA_MAX], *nla_addr = NULL;
- if (hdr->nlmsg_type != RTM_NEWADDR || ctxt->ret >= (ssize_t)ctxt->addrs_sz)
+ if (hdr->nlmsg_type != RTM_NEWADDR)
return NL_SKIP;
ifa = NLMSG_DATA(hdr);
if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
+ (ctxt->af != ifa->ifa_family) ||
(ctxt->ifindex && ifa->ifa_index != (unsigned)ctxt->ifindex))
return NL_SKIP;
nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
- if (!nla[IFA_ADDRESS])
+
+ switch (ifa->ifa_family) {
+ case AF_INET6:
+ if (nla[IFA_ADDRESS])
+ nla_addr = nla[IFA_ADDRESS];
+ break;
+
+ case AF_INET:
+ if (nla[IFA_LOCAL])
+ nla_addr = nla[IFA_LOCAL];
+ break;
+
+ default:
+ break;
+ }
+ if (!nla_addr)
return NL_SKIP;
- memset(&ctxt->addrs[ctxt->ret], 0, sizeof(ctxt->addrs[ctxt->ret]));
- ctxt->addrs[ctxt->ret].prefix = ifa->ifa_prefixlen;
+ addrs = realloc(addrs, sizeof(*addrs)*(ctxt->ret + 1));
+ if (!addrs)
+ return NL_SKIP;
+
+ memset(&addrs[ctxt->ret], 0, sizeof(addrs[ctxt->ret]));
+ addrs[ctxt->ret].prefix = ifa->ifa_prefixlen;
+
+ nla_memcpy(&addrs[ctxt->ret].addr, nla_addr,
+ sizeof(addrs[ctxt->ret].addr));
- nla_memcpy(&ctxt->addrs[ctxt->ret].addr, nla[IFA_ADDRESS],
- sizeof(ctxt->addrs[ctxt->ret].addr));
+ if (nla[IFA_BROADCAST])
+ nla_memcpy(&addrs[ctxt->ret].broadcast, nla[IFA_BROADCAST],
+ sizeof(addrs[ctxt->ret].broadcast));
if (nla[IFA_CACHEINFO]) {
struct ifa_cacheinfo *ifc = nla_data(nla[IFA_CACHEINFO]);
- ctxt->addrs[ctxt->ret].preferred = ifc->ifa_prefered;
- ctxt->addrs[ctxt->ret].valid = ifc->ifa_valid;
+ addrs[ctxt->ret].preferred = ifc->ifa_prefered;
+ addrs[ctxt->ret].valid = ifc->ifa_valid;
}
if (ifa->ifa_flags & IFA_F_DEPRECATED)
- ctxt->addrs[ctxt->ret].preferred = 0;
+ addrs[ctxt->ret].preferred = 0;
ctxt->ret++;
+ *(ctxt->addrs) = addrs;
return NL_OK;
}
return NL_STOP;
}
+static int prefix_cmp(const void *va, const void *vb)
+{
+ const struct odhcpd_ipaddr *a = va, *b = vb;
+ int ret = 0;
+
+ if (a->prefix == b->prefix) {
+ ret = (ntohl(a->addr.in.s_addr) < ntohl(b->addr.in.s_addr)) ? 1 :
+ (ntohl(a->addr.in.s_addr) > ntohl(b->addr.in.s_addr)) ? -1 : 0;
+ } else
+ ret = a->prefix < b->prefix ? 1 : -1;
+
+ return ret;
+}
+
+// compare IPv6 prefixes
+static int prefix6_cmp(const void *va, const void *vb)
+{
+ const struct odhcpd_ipaddr *a = va, *b = vb;
+ uint32_t a_pref = IN6_IS_ADDR_ULA(&a->addr.in6) ? 1 : a->preferred;
+ uint32_t b_pref = IN6_IS_ADDR_ULA(&b->addr.in6) ? 1 : b->preferred;
+ return (a_pref < b_pref) ? 1 : (a_pref > b_pref) ? -1 : 0;
+}
+
// Detect an IPV6-address currently assigned to the given interface
-ssize_t odhcpd_get_interface_addresses(int ifindex,
- struct odhcpd_ipaddr *addrs, size_t cnt)
+ssize_t odhcpd_get_interface_addresses(int ifindex, bool v6, struct odhcpd_ipaddr **addrs)
{
struct nl_msg *msg;
struct ifaddrmsg ifa = {
- .ifa_family = AF_INET6,
+ .ifa_family = v6? AF_INET6: AF_INET,
.ifa_prefixlen = 0,
.ifa_flags = 0,
.ifa_scope = 0,
struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
struct addr_info ctxt = {
.ifindex = ifindex,
+ .af = v6? AF_INET6: AF_INET,
.addrs = addrs,
- .addrs_sz = cnt,
.ret = 0,
.pending = 1,
};
nl_recvmsgs(rtnl_socket, cb);
nlmsg_free(msg);
+
+ if (ctxt.ret <= 0)
+ goto out;
+
+ time_t now = odhcpd_time();
+ struct odhcpd_ipaddr *addr = *addrs;
+
+ qsort(addr, ctxt.ret, sizeof(*addr), v6 ? prefix6_cmp : prefix_cmp);
+
+ for (ssize_t i = 0; i < ctxt.ret; ++i) {
+ if (addr[i].preferred < UINT32_MAX - now)
+ addr[i].preferred += now;
+
+ if (addr[i].valid < UINT32_MAX - now)
+ addr[i].valid += now;
+ }
+
out:
nl_cb_put(cb);
return ctxt.ret;
}
-int odhcpd_get_linklocal_interface_address(int ifindex, struct in6_addr *lladdr)
+static int odhcpd_get_linklocal_interface_address(int ifindex, struct in6_addr *lladdr)
{
int status = -1;
struct sockaddr_in6 addr = {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, ifindex};
return status;
}
+/*
+ * DNS address selection criteria order :
+ * - use IPv6 address with valid lifetime if none is yet selected
+ * - use IPv6 address with a preferred lifetime if the already selected IPv6 address is deprecated
+ * - use an IPv6 ULA address if the already selected IPv6 address is not an ULA address
+ * - use the IPv6 address with the longest preferred lifetime
+ */
+int odhcpd_get_interface_dns_addr(const struct interface *iface, struct in6_addr *addr)
+{
+ time_t now = odhcpd_time();
+ ssize_t m = -1;
+
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ if (iface->ia_addr[i].valid <= (uint32_t)now)
+ continue;
+
+ if (m < 0) {
+ m = i;
+ continue;
+ }
+
+ if (iface->ia_addr[m].preferred >= (uint32_t)now &&
+ iface->ia_addr[i].preferred < (uint32_t)now)
+ continue;
+
+ if (IN6_IS_ADDR_ULA(&iface->ia_addr[i].addr.in6)) {
+ if (!IN6_IS_ADDR_ULA(&iface->ia_addr[m].addr.in6)) {
+ m = i;
+ continue;
+ }
+ } else if (IN6_IS_ADDR_ULA(&iface->ia_addr[m].addr.in6))
+ continue;
+
+ if (iface->ia_addr[i].preferred > iface->ia_addr[m].preferred)
+ m = i;
+ }
+
+ if (m >= 0) {
+ *addr = iface->ia_addr[m].addr.in6;
+ return 0;
+ }
+
+ return odhcpd_get_linklocal_interface_address(iface->ifindex, addr);
+}
+
int odhcpd_setup_route(const struct in6_addr *addr, const int prefixlen,
const struct interface *iface, const struct in6_addr *gw,
const uint32_t metric, const bool add)
return nl_wait_for_ack(rtnl_socket);
}
+int odhcpd_setup_addr(struct odhcpd_ipaddr *addr,
+ const struct interface *iface, const bool v6,
+ const bool add)
+{
+ struct nl_msg *msg;
+ struct ifaddrmsg ifa = {
+ .ifa_family = v6 ? AF_INET6 : AF_INET,
+ .ifa_prefixlen = addr->prefix,
+ .ifa_flags = 0,
+ .ifa_scope = 0,
+ .ifa_index = iface->ifindex, };
+ int ret = 0, flags = NLM_F_REQUEST;
+
+ if (add)
+ flags |= NLM_F_REPLACE | NLM_F_CREATE;
+
+ msg = nlmsg_alloc_simple(add ? RTM_NEWADDR : RTM_DELADDR, 0);
+ if (!msg)
+ return -1;
+
+ nlmsg_append(msg, &ifa, sizeof(ifa), flags);
+ nla_put(msg, IFA_LOCAL, v6 ? 16 : 4, &addr->addr);
+ if (v6) {
+ struct ifa_cacheinfo cinfo = { .ifa_prefered = 0xffffffffU,
+ .ifa_valid = 0xffffffffU,
+ .cstamp = 0,
+ .tstamp = 0 };
+ time_t now = odhcpd_time();
+
+ if (addr->preferred) {
+ int64_t preferred = addr->preferred - now;
+ if (preferred < 0)
+ preferred = 0;
+ else if (preferred > UINT32_MAX)
+ preferred = UINT32_MAX;
+
+ cinfo.ifa_prefered = preferred;
+ }
+
+ if (addr->valid) {
+ int64_t valid = addr->valid - now;
+ if (valid <= 0) {
+ nlmsg_free(msg);
+ return -1;
+ }
+ else if (valid > UINT32_MAX)
+ valid = UINT32_MAX;
+
+ cinfo.ifa_valid = valid;
+ }
+
+ nla_put(msg, IFA_CACHEINFO, sizeof(cinfo), &cinfo);
+
+ nla_put_u32(msg, IFA_FLAGS, IFA_F_NOPREFIXROUTE);
+ } else {
+ if (addr->broadcast.s_addr)
+ nla_put_u32(msg, IFA_BROADCAST, addr->broadcast.s_addr);
+ }
+
+ ret = nl_send_auto_complete(rtnl_socket, msg);
+ nlmsg_free(msg);
+
+ if (ret < 0)
+ return ret;
+
+ return nl_wait_for_ack(rtnl_socket);
+}
+
struct interface* odhcpd_get_interface_by_index(int ifindex)
{
struct interface *iface;
{
struct odhcpd_event *e = container_of(u, struct odhcpd_event, uloop);
- uint8_t data_buf[RELAYD_BUFFER_SIZE], cmsg_buf[128];
+ uint8_t data_buf[8192], cmsg_buf[128];
union {
struct sockaddr_in6 in6;
struct sockaddr_in in;
((event->handle_error) ? ULOOP_ERROR_CB : 0));
}
+int odhcpd_deregister(struct odhcpd_event *event)
+{
+ event->uloop.cb = NULL;
+ return uloop_fd_delete(&event->uloop);
+}
+
void odhcpd_process(struct odhcpd_event *event)
{
odhcpd_receive_packets(&event->uloop, 0);
a[bytes] = (a[bytes] & mask) | ((~mask) & b[bytes]);
}
}
+
+
+int odhcpd_netmask2bitlen(bool inet6, void *mask)
+{
+ int bits;
+ struct in_addr *v4;
+ struct in6_addr *v6;
+
+ if (inet6)
+ for (bits = 0, v6 = mask;
+ bits < 128 && (v6->s6_addr[bits / 8] << (bits % 8)) & 128;
+ bits++);
+ else
+ for (bits = 0, v4 = mask;
+ bits < 32 && (ntohl(v4->s_addr) << bits) & 0x80000000;
+ bits++);
+
+ return bits;
+}
+
+bool odhcpd_bitlen2netmask(bool inet6, unsigned int bits, void *mask)
+{
+ uint8_t b;
+ struct in_addr *v4;
+ struct in6_addr *v6;
+
+ if (inet6)
+ {
+ if (bits > 128)
+ return false;
+
+ v6 = mask;
+
+ for (unsigned int i = 0; i < sizeof(v6->s6_addr); i++)
+ {
+ b = (bits > 8) ? 8 : bits;
+ v6->s6_addr[i] = (uint8_t)(0xFF << (8 - b));
+ bits -= b;
+ }
+ }
+ else
+ {
+ if (bits > 32)
+ return false;
+
+ v4 = mask;
+ v4->s_addr = bits ? htonl(~((1 << (32 - bits)) - 1)) : 0;
+ }
+
+ return true;
+}