aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pingu_iface.c2
-rw-r--r--pingu_netlink.c146
-rw-r--r--pingu_netlink.h4
3 files changed, 137 insertions, 15 deletions
diff --git a/pingu_iface.c b/pingu_iface.c
index 1cf4ca6..4eef78b 100644
--- a/pingu_iface.c
+++ b/pingu_iface.c
@@ -154,6 +154,7 @@ void pingu_iface_gw_action(struct pingu_iface *iface,
log_debug("%s: removed default gateway", iface->name);
break;
}
+ kernel_route_multipath(action, &iface_list, RT_TABLE_MAIN);
}
void pingu_iface_update_routes(struct pingu_iface *iface, int action)
@@ -162,6 +163,7 @@ void pingu_iface_update_routes(struct pingu_iface *iface, int action)
list_for_each_entry(route, &iface->gateway_list, gateway_list_entry) {
kernel_route_modify(action, route, iface, RT_TABLE_MAIN);
}
+ kernel_route_multipath(action, &iface_list, &iface->default_gw, RT_TABLE_MAIN);
}
int pingu_iface_set_route_table(struct pingu_iface *iface, int table)
diff --git a/pingu_netlink.c b/pingu_netlink.c
index 8c8f2e5..aad2a98 100644
--- a/pingu_netlink.c
+++ b/pingu_netlink.c
@@ -39,7 +39,7 @@
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
-#endif
+#endif
#ifndef TRUE
#define TRUE 1
@@ -118,6 +118,37 @@ static int netlink_add_rtattr_addr_any(struct nlmsghdr *n, int maxlen,
return FALSE;
}
+static int netlink_add_subrtattr_l(struct rtattr *rta, int maxlen, int type,
+ const void *data, int alen)
+{
+ struct rtattr *subrta;
+ int len = RTA_LENGTH(alen);
+
+ if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen)
+ return FALSE;
+
+ subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), data, alen);
+ rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
+ return TRUE;
+}
+
+static int netlink_add_subrtattr_addr_any(struct rtattr *rta, int maxlen,
+ int type, union sockaddr_any *sa)
+{
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ return netlink_add_subrtattr_l(rta, maxlen, type, &sa->sin.sin_addr, 4);
+ break;
+ case AF_INET6:
+ return netlink_add_subrtattr_l(rta, maxlen, type, &sa->sin6.sin6_addr, 16);
+ break;
+ }
+ return FALSE;
+}
+
static int netlink_log_error(struct nlmsghdr *hdr)
{
struct nlmsgerr *nlerr = (struct nlmsgerr*)NLMSG_DATA(hdr);
@@ -240,8 +271,8 @@ int netlink_route_modify(struct netlink_fd *fd, int action_type,
req.msg.rtm_protocol = route->protocol;
req.msg.rtm_scope = route->scope;
req.msg.rtm_type = route->type;
-
- netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_DST,
+
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_DST,
&route->dest);
netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_GATEWAY,
&route->gw_addr);
@@ -254,6 +285,88 @@ int netlink_route_modify(struct netlink_fd *fd, int action_type,
(struct sockaddr *) &addr, sizeof(addr));
}
+static int add_one_nh(struct rtattr *rta, struct rtnexthop *rtnh,
+ struct pingu_iface *iface)
+{
+ struct pingu_gateway *route = container_of(iface->gateway_list.next,
+ struct pingu_gateway,
+ gateway_list_entry);
+ netlink_add_subrtattr_addr_any(rta, 1024, RTA_GATEWAY,
+ &route->gw_addr);
+ rtnh->rtnh_len += sizeof(struct rtattr) + 4; // TODO: support ipv6
+ rtnh->rtnh_ifindex = iface->index;
+ return 1;
+}
+
+static int add_nexthops(struct nlmsghdr *nlh, size_t nlh_size,
+ struct list_head *iface_list)
+{
+ char buf[1024];
+ struct rtattr *rta = (void *)buf;
+ struct rtnexthop *rtnh;
+ struct pingu_iface *iface;
+ int count = 0;
+
+ memset(buf, 0, sizeof(buf));
+ rta->rta_type = RTA_MULTIPATH;
+ rta->rta_len = RTA_LENGTH(0);
+ rtnh = RTA_DATA(rta);
+
+ list_for_each_entry(iface, iface_list, iface_list_entry) {
+ if (iface->index == 0 || list_empty(&iface->gateway_list))
+ continue;
+ memset(rtnh, 0, sizeof(*rtnh));
+ rtnh->rtnh_len = sizeof(*rtnh);
+ rta->rta_len += rtnh->rtnh_len;
+ count += add_one_nh(rta, rtnh, iface);
+ rtnh = RTNH_NEXT(rtnh);
+ }
+ if (rta->rta_len > RTA_LENGTH(0))
+ netlink_add_rtattr_l(nlh, nlh_size, RTA_MULTIPATH,
+ RTA_DATA(rta), RTA_PAYLOAD(rta));
+ return count;
+}
+
+int netlink_route_multipath(struct netlink_fd *fd, int action_type,
+ struct list_head *iface_list, int table)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg msg;
+ char buf[1024];
+ } req;
+ struct sockaddr_nl addr;
+ union sockaddr_any dest;
+
+ memset(&req, 0, sizeof(req));
+ memset(&addr, 0, sizeof(addr));
+ memset(&dest, 0, sizeof(dest));
+
+ dest.sa.sa_family = AF_INET;
+
+ addr.nl_family = AF_NETLINK;
+
+ req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.nlh.nlmsg_flags = NLM_F_REQUEST;
+ req.nlh.nlmsg_type = action_type;
+ if (action_type == RTM_NEWROUTE)
+ req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ req.msg.rtm_family = AF_INET;
+ req.msg.rtm_table = table;
+ req.msg.rtm_dst_len = 0;
+ req.msg.rtm_protocol = RTPROT_UNSPEC;
+ req.msg.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.msg.rtm_type = RTN_UNICAST;
+
+ netlink_add_rtattr_addr_any(&req.nlh, sizeof(req), RTA_DST,
+ &dest);
+
+ add_nexthops(&req.nlh, sizeof(req), iface_list);
+ return sendto(fd->fd, (void *) &req, sizeof(req), 0,
+ (struct sockaddr *) &addr, sizeof(addr));
+}
+
int netlink_route_replace_or_add(struct netlink_fd *fd,
struct pingu_gateway *route,
int iface_index, int table)
@@ -261,12 +374,12 @@ int netlink_route_replace_or_add(struct netlink_fd *fd,
return netlink_route_modify(fd, RTM_NEWROUTE, route, iface_index, table);
}
-int netlink_route_delete(struct netlink_fd *fd,
+int netlink_route_delete(struct netlink_fd *fd,
struct pingu_gateway *route,
int iface_index, int table)
{
return netlink_route_modify(fd, RTM_DELROUTE, route, iface_index, table);
-}
+}
int netlink_rule_modify(struct netlink_fd *fd,
struct pingu_iface *iface, int type)
@@ -290,7 +403,7 @@ int netlink_rule_modify(struct netlink_fd *fd,
req.nlh.nlmsg_type = type;
if (type == RTM_NEWRULE)
req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
-
+
req.msg.rtm_family = AF_INET;
req.msg.rtm_table = iface->route_table;
req.msg.rtm_protocol = RTPROT_BOOT;
@@ -320,7 +433,7 @@ int netlink_rule_replace_or_add(struct netlink_fd *fd, struct pingu_iface *iface
netlink_rule_del(fd, iface);
return netlink_rule_modify(fd, iface, RTM_NEWRULE);
}
-
+
static void netlink_link_new_cb(struct nlmsghdr *msg)
{
struct pingu_iface *iface;
@@ -426,10 +539,10 @@ static struct pingu_gateway *gw_from_rtmsg(struct pingu_gateway *gw,
gw->protocol = rtm->rtm_protocol;
gw->scope = rtm->rtm_scope;
gw->type = rtm->rtm_type;
-
+
if (rta[RTA_SRC] != NULL)
sockaddr_init(&gw->src, rtm->rtm_family, RTA_DATA(rta[RTA_SRC]));
-
+
if (rta[RTA_DST] != NULL)
sockaddr_init(&gw->dest, rtm->rtm_family, RTA_DATA(rta[RTA_DST]));
@@ -452,7 +565,7 @@ static void log_route_change(struct pingu_gateway *route,
sockaddr_to_string(&route->dest, deststr, sizeof(deststr));
sockaddr_to_string(&route->gw_addr, gwstr, sizeof(gwstr));
log_debug("%s route to %s/%i via %s dev %s table %i", actionstr,
- deststr, route->dst_len, gwstr, ifname, table);
+ deststr, route->dst_len, gwstr, ifname, table);
}
static void netlink_route_cb_action(struct nlmsghdr *msg, int action)
@@ -462,13 +575,13 @@ static void netlink_route_cb_action(struct nlmsghdr *msg, int action)
struct rtattr *rta[RTA_MAX+1];
struct pingu_gateway route;
-
+
/* ignore route changes that we made ourselves via talk_fd */
if (msg->nlmsg_pid == getpid())
return;
-
+
netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(rtm), RTM_PAYLOAD(msg));
- if (rta[RTA_OIF] == NULL || rtm->rtm_family != PF_INET
+ if (rta[RTA_OIF] == NULL || rtm->rtm_family != PF_INET
|| rtm->rtm_table != RT_TABLE_MAIN)
return;
@@ -572,6 +685,11 @@ int kernel_route_modify(int action, struct pingu_gateway *route,
return netlink_route_modify(&talk_fd, action, route, iface->index, table);
}
+int kernel_route_multipath(int action, struct list_head *iface_list, int table)
+{
+ return netlink_route_multipath(&talk_fd, action, iface_list, table);
+}
+
int kernel_init(struct ev_loop *loop)
{
int i;
@@ -592,7 +710,7 @@ int kernel_init(struct ev_loop *loop)
/* man page netlink(7) says that first created netlink socket will
* get the getpid() assigned as nlmsg_pid. This is our talk_fd.
- *
+ *
* Our route callbacks will ignore route changes made by ourselves
* (nlmsg_pid == getpid()) but we still need to get the initial
* route enumration. Therefore we use another netlink socket to
diff --git a/pingu_netlink.h b/pingu_netlink.h
index 95fb6f3..bb9907f 100644
--- a/pingu_netlink.h
+++ b/pingu_netlink.h
@@ -6,5 +6,7 @@
int kernel_init(struct ev_loop *loop);
int kernel_route_modify(int action, struct pingu_gateway *route,
- struct pingu_iface *iface, int table);
+ struct pingu_iface *iface, int table);
+int kernel_route_multipath(int action, struct list_head *iface_list, int table);
+
#endif