+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);
+}