• Kirill Smelkov's avatar
    X ipv6: route: Don't turn "multicast to loopback" routes into rejects · 25bcc760
    Kirill Smelkov authored
    With multicast routing, if one wants to forward multicast traffic from
    external interfaces to lo, there must be a "daddr=group oif=lo" regular
    route in the system, because both IPv4 and IPv4 multicast-routing code
    complete forwarding of multicast packets by sending it to this regular
    route:
    
        IPv4:  (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/ipmr.c?id=v5.18-rc5-28-ga7391ad35724#n1844)
    	ipmr_queue_xmit
    		rt = ip_route_output_ports(daddr, oif=vif->link)
    		...
    
        IPv6:  (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv6/ip6mr.c?id=v5.18-rc5-28-ga7391ad35724#n2046)
    	ip6mr_forward2
    		fl6 = (struct flowi6) {
    			.flowi6_oif = vif->link,
    			.daddr = ipv6h->daddr,
    		};
    		dst = ip6_route_output(net, NULL, &fl6);
    
    For IPv4 the system does not reject setting up such a route, for example the
    following works ok:
    
        ip route add multicast 224.0.0.0/4 oif lo dev lo scope global
    
    and, even if such route is not explicitly setup, IPv4 continues to
    deliver packets to destination device due to the following ad-hoc tweak
    inside ip_route_output_key_hash_rcu:
    
    	(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/route.c?id=v5.18-rc5-28-ga7391ad35724#n2747)
    
    	err = fib_lookup(net, fl4, res, 0);
    	if (err) {
    		res->fi = NULL;
    		res->table = NULL;
    		if (fl4->flowi4_oif &&
    		    (ipv4_is_multicast(fl4->daddr) || !fl4->flowi4_l3mdev)) {
    			/* Apparently, routing tables are wrong. Assume,
    			 * that the destination is on link.
    			 *
    			 * WHY? DW.
    			 * Because we are allowed to send to iface
    			 * even if it has NO routes and NO assigned
    			 * addresses. When oif is specified, routing
    			 * tables are looked up with only one purpose:
    			 * to catch if destination is gatewayed, rather than
    			 * direct. Moreover, if MSG_DONTROUTE is set,
    			 * we send packet, ignoring both routing tables
    			 * and ifaddr state. --ANK
    			 *
    			 *
    			 * We could make it even if oif is unknown,
    			 * likely IPv6, but we do not.
    			 */
    
    			if (fl4->saddr == 0)
    				fl4->saddr = inet_select_addr(dev_out, 0,
    							      RT_SCOPE_LINK);
    			res->type = RTN_UNICAST;
    			goto make_route;
    
    However for IPv6, even if e.g. the following seemingly succeeds:
    
    	ip route add multicast ff1e::/16 oif lo dev lo scope global
    
    the systems automatically turns it into reject:
    
    	(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv6/route.c?id=v5.18-rc5-28-ga7391ad35724#n3805)
    
    	ip6_route_info_create
    		/* We cannot add true routes via loopback here, they would
    		 * result in kernel looping; promote them to reject routes
    		 */
    		addr_type = ipv6_addr_type(&cfg->fc_dst);
    		if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev, addr_type))
    			rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
    
    which makes the route essentially disabled because dst, that ip6_route_output
    will lookup, will come with dst->dev=lo (ok), but dst->output=ip6_pkt_discard_out
    which will not send the packet to anywhere.
    
    Turning lo-routes into rejects was first added in Linux 2.1.90pre1 (1998) as
    
    	(9d11a517 in historic repository)
    
    		ip6_route_add()
    		...
    	+	/* We cannot add true routes via loopback here,
    	+	   they would result in kernel looping; promote them to reject routes
    	+	 */
    	+	if ((rtmsg->rtmsg_flags&RTF_REJECT) ||
    	+	    (dev && (dev->flags&IFF_LOOPBACK) && !(addr_type&IPV6_ADDR_LOOPBACK))) {
    	+		dev = dev_get("lo");
    	+		rt->u.dst.output = ip6_pkt_discard;
    	+		rt->u.dst.input = ip6_pkt_discard;
    	+		rt->u.dst.error = -EHOSTUNREACH;
    	+		rt->rt6i_flags = RTF_REJECT|RTF_NONEXTHOP;
    	+		rt->rt6i_metric = rtmsg->rtmsg_metric;
    	+		rt->rt6i_dev = dev;
    	+		goto install_route;
    	+	}
    
    XXX however with multicast there should not be cycles because when multicast
    skb is forwarded, it is marked with IP6SKB_FORWARDED, and on next turn not
    processed on input if seen with this flag (see 7bc570c8 "[IPV6] MROUTE:
    Support multicast forwarding.", git.kernel.org/linus/7bc570c8)
    
    -> Allow establishing routes for multicast to loopback.
    
    XXX verify this in detail + add test that if we enable such forwarding we
    cannot put the system into infinite loop.
    25bcc760
route.c 167 KB