Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6d460cf authored by David S. Miller's avatar David S. Miller
Browse files

ipv4: Make icmp route lookup code a bit clearer.



The route lookup code in icmp_send() is slightly tricky as a result of
having to handle all of the requirements of RFC 4301 host relookups.

Pull the route resolution into a seperate function, so that the error
handling and route reference counting is hopefully easier to see and
contained wholly within this new routine.

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 2774c131
Loading
Loading
Loading
Loading
+96 −79
Original line number Diff line number Diff line
@@ -369,6 +369,98 @@ static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
	icmp_xmit_unlock(sk);
}

static struct rtable *icmp_route_lookup(struct net *net, struct sk_buff *skb_in,
					struct iphdr *iph,
					__be32 saddr, u8 tos,
					int type, int code,
					struct icmp_bxm *param)
{
	struct flowi fl = {
		.fl4_dst = (param->replyopts.srr ?
			    param->replyopts.faddr : iph->saddr),
		.fl4_src = saddr,
		.fl4_tos = RT_TOS(tos),
		.proto = IPPROTO_ICMP,
		.fl_icmp_type = type,
		.fl_icmp_code = code,
	};
	struct rtable *rt, *rt2;
	int err;

	security_skb_classify_flow(skb_in, &fl);
	err = __ip_route_output_key(net, &rt, &fl);
	if (err)
		return ERR_PTR(err);

	/* No need to clone since we're just using its address. */
	rt2 = rt;

	if (!fl.fl4_src)
		fl.fl4_src = rt->rt_src;

	err = xfrm_lookup(net, (struct dst_entry **)&rt, &fl, NULL, 0);
	switch (err) {
	case 0:
		if (rt != rt2)
			return rt;
		break;
	case -EPERM:
		rt = NULL;
		break;
	default:
		return ERR_PTR(err);
	}

	err = xfrm_decode_session_reverse(skb_in, &fl, AF_INET);
	if (err)
		goto relookup_failed;

	if (inet_addr_type(net, fl.fl4_src) == RTN_LOCAL) {
		err = __ip_route_output_key(net, &rt2, &fl);
	} else {
		struct flowi fl2 = {};
		unsigned long orefdst;

		fl2.fl4_dst = fl.fl4_src;
		err = ip_route_output_key(net, &rt2, &fl2);
		if (err)
			goto relookup_failed;
		/* Ugh! */
		orefdst = skb_in->_skb_refdst; /* save old refdst */
		err = ip_route_input(skb_in, fl.fl4_dst, fl.fl4_src,
				     RT_TOS(tos), rt2->dst.dev);

		dst_release(&rt2->dst);
		rt2 = skb_rtable(skb_in);
		skb_in->_skb_refdst = orefdst; /* restore old refdst */
	}

	if (err)
		goto relookup_failed;

	err = xfrm_lookup(net, (struct dst_entry **)&rt2, &fl, NULL,
			  XFRM_LOOKUP_ICMP);
	switch (err) {
	case 0:
		dst_release(&rt->dst);
		rt = rt2;
		break;
	case -EPERM:
		return ERR_PTR(err);
	default:
		if (!rt)
			return ERR_PTR(err);
		break;
	}


	return rt;

relookup_failed:
	if (rt)
		return rt;
	return ERR_PTR(err);
}

/*
 *	Send an ICMP message in response to a situation
@@ -506,86 +598,11 @@ void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
	ipc.opt = &icmp_param.replyopts;
	ipc.tx_flags = 0;

	{
		struct flowi fl = {
			.fl4_dst = icmp_param.replyopts.srr ?
				   icmp_param.replyopts.faddr : iph->saddr,
			.fl4_src = saddr,
			.fl4_tos = RT_TOS(tos),
			.proto = IPPROTO_ICMP,
			.fl_icmp_type = type,
			.fl_icmp_code = code,
		};
		int err;
		struct rtable *rt2;

		security_skb_classify_flow(skb_in, &fl);
		if (__ip_route_output_key(net, &rt, &fl))
			goto out_unlock;

		/* No need to clone since we're just using its address. */
		rt2 = rt;

		if (!fl.nl_u.ip4_u.saddr)
			fl.nl_u.ip4_u.saddr = rt->rt_src;

		err = xfrm_lookup(net, (struct dst_entry **)&rt, &fl, NULL, 0);
		switch (err) {
		case 0:
			if (rt != rt2)
				goto route_done;
			break;
		case -EPERM:
			rt = NULL;
			break;
		default:
	rt = icmp_route_lookup(net, skb_in, iph, saddr, tos,
			       type, code, &icmp_param);
	if (IS_ERR(rt))
		goto out_unlock;
		}

		if (xfrm_decode_session_reverse(skb_in, &fl, AF_INET))
			goto relookup_failed;

		if (inet_addr_type(net, fl.fl4_src) == RTN_LOCAL)
			err = __ip_route_output_key(net, &rt2, &fl);
		else {
			struct flowi fl2 = {};
			unsigned long orefdst;

			fl2.fl4_dst = fl.fl4_src;
			if (ip_route_output_key(net, &rt2, &fl2))
				goto relookup_failed;

			/* Ugh! */
			orefdst = skb_in->_skb_refdst; /* save old refdst */
			err = ip_route_input(skb_in, fl.fl4_dst, fl.fl4_src,
					     RT_TOS(tos), rt2->dst.dev);

			dst_release(&rt2->dst);
			rt2 = skb_rtable(skb_in);
			skb_in->_skb_refdst = orefdst; /* restore old refdst */
		}

		if (err)
			goto relookup_failed;

		err = xfrm_lookup(net, (struct dst_entry **)&rt2, &fl, NULL,
				  XFRM_LOOKUP_ICMP);
		switch (err) {
		case 0:
			dst_release(&rt->dst);
			rt = rt2;
			break;
		case -EPERM:
			goto ende;
		default:
relookup_failed:
			if (!rt)
				goto out_unlock;
			break;
		}
	}

route_done:
	if (!icmpv4_xrlim_allow(net, rt, type, code))
		goto ende;