summaryrefslogtreecommitdiff
path: root/sysdep/linux
diff options
context:
space:
mode:
Diffstat (limited to 'sysdep/linux')
-rw-r--r--sysdep/linux/netlink/netlink.c178
1 files changed, 157 insertions, 21 deletions
diff --git a/sysdep/linux/netlink/netlink.c b/sysdep/linux/netlink/netlink.c
index 25bcadf9..3eaa53ae 100644
--- a/sysdep/linux/netlink/netlink.c
+++ b/sysdep/linux/netlink/netlink.c
@@ -238,37 +238,143 @@ nl_parse_attrs(struct rtattr *a, struct rtattr **k, int ksize)
return 1;
}
-static void
-nl_add_attr_u32(struct nlmsghdr *h, unsigned maxsize, int code, u32 data)
+void
+nl_add_attr(struct nlmsghdr *h, unsigned bufsize, unsigned code,
+ void *data, unsigned dlen)
{
- unsigned len = RTA_LENGTH(4);
+ unsigned len = RTA_LENGTH(dlen);
+ unsigned pos = NLMSG_ALIGN(h->nlmsg_len);
struct rtattr *a;
- if (NLMSG_ALIGN(h->nlmsg_len) + len > maxsize)
- bug("nl_add_attr32: packet buffer overflow");
- a = (struct rtattr *)((char *)h + NLMSG_ALIGN(h->nlmsg_len));
+ if (pos + len > bufsize)
+ bug("nl_add_attr: packet buffer overflow");
+ a = (struct rtattr *)((char *)h + pos);
a->rta_type = code;
a->rta_len = len;
- memcpy(RTA_DATA(a), &data, 4);
- h->nlmsg_len = NLMSG_ALIGN(h->nlmsg_len) + len;
+ h->nlmsg_len = pos + len;
+ memcpy(RTA_DATA(a), data, dlen);
}
-static void
-nl_add_attr_ipa(struct nlmsghdr *h, unsigned maxsize, int code, ip_addr ipa)
+static inline void
+nl_add_attr_u32(struct nlmsghdr *h, unsigned bufsize, int code, u32 data)
{
- unsigned len = RTA_LENGTH(sizeof(ipa));
- struct rtattr *a;
+ nl_add_attr(h, bufsize, code, &data, 4);
+}
- if (NLMSG_ALIGN(h->nlmsg_len) + len > maxsize)
- bug("nl_add_attr_ipa: packet buffer overflow");
- a = (struct rtattr *)((char *)h + NLMSG_ALIGN(h->nlmsg_len));
- a->rta_type = code;
- a->rta_len = len;
+static inline void
+nl_add_attr_ipa(struct nlmsghdr *h, unsigned bufsize, int code, ip_addr ipa)
+{
ipa_hton(ipa);
- memcpy(RTA_DATA(a), &ipa, sizeof(ipa));
- h->nlmsg_len = NLMSG_ALIGN(h->nlmsg_len) + len;
+ nl_add_attr(h, bufsize, code, &ipa, sizeof(ipa));
+}
+
+#define RTNH_SIZE (sizeof(struct rtnexthop) + sizeof(struct rtattr) + sizeof(ip_addr))
+
+static inline void
+add_mpnexthop(char *buf, ip_addr ipa, unsigned iface, unsigned char weight)
+{
+ struct rtnexthop *nh = (void *) buf;
+ struct rtattr *rt = (void *) (buf + sizeof(*nh));
+ nh->rtnh_len = RTNH_SIZE;
+ nh->rtnh_flags = 0;
+ nh->rtnh_hops = weight;
+ nh->rtnh_ifindex = iface;
+ rt->rta_len = sizeof(*rt) + sizeof(ipa);
+ rt->rta_type = RTA_GATEWAY;
+ ipa_hton(ipa);
+ memcpy(buf + sizeof(*nh) + sizeof(*rt), &ipa, sizeof(ipa));
+}
+
+
+static void
+nl_add_multipath(struct nlmsghdr *h, unsigned bufsize, struct mpnh *nh)
+{
+ unsigned len = sizeof(struct rtattr);
+ unsigned pos = NLMSG_ALIGN(h->nlmsg_len);
+ char *buf = (char *)h + pos;
+ struct rtattr *rt = (void *) buf;
+ buf += len;
+
+ for (; nh; nh = nh->next)
+ {
+ len += RTNH_SIZE;
+ if (pos + len > bufsize)
+ bug("nl_add_multipath: packet buffer overflow");
+
+ add_mpnexthop(buf, nh->gw, nh->iface->index, nh->weight);
+ buf += RTNH_SIZE;
+ }
+
+ rt->rta_type = RTA_MULTIPATH;
+ rt->rta_len = len;
+ h->nlmsg_len = pos + len;
}
+
+static struct mpnh *
+nl_parse_multipath(struct krt_proto *p, struct rtattr *ra)
+{
+ /* Temporary buffer for multicast nexthops */
+ static struct mpnh *nh_buffer;
+ static int nh_buf_size; /* in number of structures */
+ static int nh_buf_used;
+
+ struct rtattr *a[RTA_CACHEINFO+1];
+ struct rtnexthop *nh = RTA_DATA(ra);
+ struct mpnh *rv, *first, **last;
+ int len = RTA_PAYLOAD(ra);
+
+ first = NULL;
+ last = &first;
+ nh_buf_used = 0;
+
+ while (len)
+ {
+ /* Use RTNH_OK(nh,len) ?? */
+ if ((len < sizeof(*nh)) || (len < nh->rtnh_len))
+ return NULL;
+
+ if (nh_buf_used == nh_buf_size)
+ {
+ nh_buf_size = nh_buf_size ? (nh_buf_size * 2) : 4;
+ nh_buffer = xrealloc(nh_buffer, nh_buf_size * sizeof(struct mpnh));
+ }
+ *last = rv = nh_buffer + nh_buf_used++;
+ rv->next = NULL;
+ last = &(rv->next);
+
+ rv->weight = nh->rtnh_hops;
+ rv->iface = if_find_by_index(nh->rtnh_ifindex);
+ if (!rv->iface)
+ return NULL;
+
+ /* Nonexistent RTNH_PAYLOAD ?? */
+ nl_attr_len = nh->rtnh_len - RTNH_LENGTH(0);
+ nl_parse_attrs(RTNH_DATA(nh), a, sizeof(a));
+ if (a[RTA_GATEWAY])
+ {
+ if (RTA_PAYLOAD(a[RTA_GATEWAY]) != sizeof(ip_addr))
+ return NULL;
+
+ memcpy(&rv->gw, RTA_DATA(a[RTA_GATEWAY]), sizeof(ip_addr));
+ ipa_ntoh(rv->gw);
+
+ neighbor *ng = neigh_find2(&p->p, &rv->gw, rv->iface,
+ (nh->rtnh_flags & RTNH_F_ONLINK) ? NEF_ONLINK : 0);
+ if (!ng || (ng->scope == SCOPE_HOST))
+ return NULL;
+ }
+ else
+ return NULL;
+
+ len -= NLMSG_ALIGN(nh->rtnh_len);
+ nh = RTNH_NEXT(nh);
+ }
+
+ return first;
+}
+
+
/*
* Scanning of interfaces
*/
@@ -490,6 +596,7 @@ krt_capable(rte *e)
case RTD_BLACKHOLE:
case RTD_UNREACHABLE:
case RTD_PROHIBIT:
+ case RTD_MULTIPATH:
break;
default:
return 0;
@@ -497,6 +604,15 @@ krt_capable(rte *e)
return 1;
}
+static inline int
+nh_bufsize(struct mpnh *nh)
+{
+ int rv = 0;
+ for (; nh != NULL; nh = nh->next)
+ rv += RTNH_SIZE;
+ return rv;
+}
+
static void
nl_send_route(struct krt_proto *p, rte *e, int new)
{
@@ -505,7 +621,7 @@ nl_send_route(struct krt_proto *p, rte *e, int new)
struct {
struct nlmsghdr h;
struct rtmsg r;
- char buf[128];
+ char buf[64 + nh_bufsize(a->nexthops)];
} r;
DBG("nl_send_route(%I/%d,new=%d)\n", net->n.prefix, net->n.pxlen, new);
@@ -531,7 +647,7 @@ nl_send_route(struct krt_proto *p, rte *e, int new)
/* a->iface != NULL checked in krt_capable() */
if (ipa_has_link_scope(a->gw))
- nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index);
+ nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index);
break;
case RTD_DEVICE:
@@ -549,6 +665,10 @@ nl_send_route(struct krt_proto *p, rte *e, int new)
case RTD_PROHIBIT:
r.r.rtm_type = RTN_PROHIBIT;
break;
+ case RTD_MULTIPATH:
+ r.r.rtm_type = RTN_UNICAST;
+ nl_add_multipath(&r.h, sizeof(r), a->nexthops);
+ break;
default:
bug("krt_capable inconsistent with nl_send_route");
}
@@ -566,6 +686,7 @@ krt_set_notify(struct krt_proto *p, net *n UNUSED, rte *new, rte *old)
nl_send_route(p, new, 1);
}
+
#define SKIP(ARG...) do { DBG("KRT: Ignoring route - " ARG); return; } while(0)
static void
@@ -671,6 +792,21 @@ nl_parse_route(struct nlmsghdr *h, int scan)
switch (i->rtm_type)
{
case RTN_UNICAST:
+
+ if (a[RTA_MULTIPATH])
+ {
+ ra.dest = RTD_MULTIPATH;
+ ra.nexthops = nl_parse_multipath(p, a[RTA_MULTIPATH]);
+ if (!ra.nexthops)
+ {
+ log(L_ERR "KRT: Received strange multipath route %I/%d",
+ net->n.prefix, net->n.pxlen);
+ return;
+ }
+
+ break;
+ }
+
ra.iface = if_find_by_index(oif);
if (!ra.iface)
{