Commit d1566268 authored by David Ahern's avatar David Ahern Committed by David S. Miller
Browse files

ipv4: Allow ipv6 gateway with ipv4 routes



Add support for RTA_VIA and allow an IPv6 nexthop for v4 routes:
   $ ip ro add 172.16.1.0/24 via inet6 2001:db8::1 dev eth0
   $ ip ro ls
   ...
   172.16.1.0/24 via inet6 2001:db8::1 dev eth0

For convenience and simplicity, userspace can use RTA_VIA to specify
AF_INET or AF_INET6 gateway.

The common fib_nexthop_info dump function compares the gateway address
family to the nh_common family to know if the gateway should be encoded
as RTA_VIA or RTA_GATEWAY.

Signed-off-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 19a9d136
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -401,6 +401,8 @@ static inline bool fib4_rules_early_flow_dissect(struct net *net,
/* Exported by fib_frontend.c */
extern const struct nla_policy rtm_ipv4_policy[];
void ip_fib_init(void);
int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,
		    struct netlink_ext_ack *extack);
__be32 fib_compute_spec_dst(struct sk_buff *skb);
bool fib_info_nh_uses_dev(struct fib_info *fi, const struct net_device *dev);
int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,
+57 −3
Original line number Diff line number Diff line
@@ -665,10 +665,55 @@ const struct nla_policy rtm_ipv4_policy[RTA_MAX + 1] = {
	[RTA_DPORT]		= { .type = NLA_U16 },
};

int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,
		    struct netlink_ext_ack *extack)
{
	struct rtvia *via;
	int alen;

	if (nla_len(nla) < offsetof(struct rtvia, rtvia_addr)) {
		NL_SET_ERR_MSG(extack, "Invalid attribute length for RTA_VIA");
		return -EINVAL;
	}

	via = nla_data(nla);
	alen = nla_len(nla) - offsetof(struct rtvia, rtvia_addr);

	switch (via->rtvia_family) {
	case AF_INET:
		if (alen != sizeof(__be32)) {
			NL_SET_ERR_MSG(extack, "Invalid IPv4 address in RTA_VIA");
			return -EINVAL;
		}
		cfg->fc_gw_family = AF_INET;
		cfg->fc_gw4 = *((__be32 *)via->rtvia_addr);
		break;
	case AF_INET6:
#ifdef CONFIG_IPV6
		if (alen != sizeof(struct in6_addr)) {
			NL_SET_ERR_MSG(extack, "Invalid IPv6 address in RTA_VIA");
			return -EINVAL;
		}
		cfg->fc_gw_family = AF_INET6;
		cfg->fc_gw6 = *((struct in6_addr *)via->rtvia_addr);
#else
		NL_SET_ERR_MSG(extack, "IPv6 support not enabled in kernel");
		return -EINVAL;
#endif
		break;
	default:
		NL_SET_ERR_MSG(extack, "Unsupported address family in RTA_VIA");
		return -EINVAL;
	}

	return 0;
}

static int rtm_to_fib_config(struct net *net, struct sk_buff *skb,
			     struct nlmsghdr *nlh, struct fib_config *cfg,
			     struct netlink_ext_ack *extack)
{
	bool has_gw = false, has_via = false;
	struct nlattr *attr;
	int err, remaining;
	struct rtmsg *rtm;
@@ -709,13 +754,16 @@ static int rtm_to_fib_config(struct net *net, struct sk_buff *skb,
			cfg->fc_oif = nla_get_u32(attr);
			break;
		case RTA_GATEWAY:
			has_gw = true;
			cfg->fc_gw_family = AF_INET;
			cfg->fc_gw4 = nla_get_be32(attr);
			break;
		case RTA_VIA:
			NL_SET_ERR_MSG(extack, "IPv4 does not support RTA_VIA attribute");
			err = -EINVAL;
			has_via = true;
			err = fib_gw_from_via(cfg, attr, extack);
			if (err)
				goto errout;
			break;
		case RTA_PRIORITY:
			cfg->fc_priority = nla_get_u32(attr);
			break;
@@ -754,6 +802,12 @@ static int rtm_to_fib_config(struct net *net, struct sk_buff *skb,
		}
	}

	if (has_gw && has_via) {
		NL_SET_ERR_MSG(extack,
			       "Nexthop configuration can not contain both GATEWAY and VIA");
		goto errout;
	}

	return 0;
errout:
	return err;
+64 −5
Original line number Diff line number Diff line
@@ -606,12 +606,22 @@ static int fib_get_nhs(struct fib_info *fi, struct rtnexthop *rtnh,

		attrlen = rtnh_attrlen(rtnh);
		if (attrlen > 0) {
			struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
			struct nlattr *nla, *nlav, *attrs = rtnh_attrs(rtnh);

			nla = nla_find(attrs, attrlen, RTA_GATEWAY);
			nlav = nla_find(attrs, attrlen, RTA_VIA);
			if (nla && nlav) {
				NL_SET_ERR_MSG(extack,
					       "Nexthop configuration can not contain both GATEWAY and VIA");
				return -EINVAL;
			}
			if (nla) {
				fib_cfg.fc_gw_family = AF_INET;
				fib_cfg.fc_gw4 = nla_get_in_addr(nla);
			} else if (nlav) {
				ret = fib_gw_from_via(&fib_cfg, nlav, extack);
				if (ret)
					goto errout;
			}

			nla = nla_find(attrs, attrlen, RTA_FLOW);
@@ -792,11 +802,43 @@ int fib_nh_match(struct fib_config *cfg, struct fib_info *fi,

		attrlen = rtnh_attrlen(rtnh);
		if (attrlen > 0) {
			struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
			struct nlattr *nla, *nlav, *attrs = rtnh_attrs(rtnh);

			nla = nla_find(attrs, attrlen, RTA_GATEWAY);
			if (nla && nla_get_in_addr(nla) != nh->fib_nh_gw4)
			nlav = nla_find(attrs, attrlen, RTA_VIA);
			if (nla && nlav) {
				NL_SET_ERR_MSG(extack,
					       "Nexthop configuration can not contain both GATEWAY and VIA");
				return -EINVAL;
			}

			if (nla) {
				if (nh->fib_nh_gw_family != AF_INET ||
				    nla_get_in_addr(nla) != nh->fib_nh_gw4)
					return 1;
			} else if (nlav) {
				struct fib_config cfg2;
				int err;

				err = fib_gw_from_via(&cfg2, nlav, extack);
				if (err)
					return err;

				switch (nh->fib_nh_gw_family) {
				case AF_INET:
					if (cfg2.fc_gw_family != AF_INET ||
					    cfg2.fc_gw4 != nh->fib_nh_gw4)
						return 1;
					break;
				case AF_INET6:
					if (cfg2.fc_gw_family != AF_INET6 ||
					    ipv6_addr_cmp(&cfg2.fc_gw6,
							  &nh->fib_nh_gw6))
						return 1;
					break;
				}
			}

#ifdef CONFIG_IP_ROUTE_CLASSID
			nla = nla_find(attrs, attrlen, RTA_FLOW);
			if (nla && nla_get_u32(nla) != nh->nh_tclassid)
@@ -1429,8 +1471,25 @@ int fib_nexthop_info(struct sk_buff *skb, const struct fib_nh_common *nhc,
			goto nla_put_failure;
		break;
	case AF_INET6:
		if (nla_put_in6_addr(skb, RTA_GATEWAY, &nhc->nhc_gw.ipv6) < 0)
		/* if gateway family does not match nexthop family
		 * gateway is encoded as RTA_VIA
		 */
		if (nhc->nhc_gw_family != nhc->nhc_family) {
			int alen = sizeof(struct in6_addr);
			struct nlattr *nla;
			struct rtvia *via;

			nla = nla_reserve(skb, RTA_VIA, alen + 2);
			if (!nla)
				goto nla_put_failure;

			via = nla_data(nla);
			via->rtvia_family = AF_INET6;
			memcpy(via->rtvia_addr, &nhc->nhc_gw.ipv6, alen);
		} else if (nla_put_in6_addr(skb, RTA_GATEWAY,
					    &nhc->nhc_gw.ipv6) < 0) {
			goto nla_put_failure;
		}
		break;
	}