diff options
Diffstat (limited to 'net/ipv4/fib_frontend.c')
| -rw-r--r-- | net/ipv4/fib_frontend.c | 101 | 
1 files changed, 88 insertions, 13 deletions
diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index a373a259253..02c3ba61884 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -722,12 +722,17 @@ void fib_add_ifaddr(struct in_ifaddr *ifa)  	}  } -static void fib_del_ifaddr(struct in_ifaddr *ifa) +/* Delete primary or secondary address. + * Optionally, on secondary address promotion consider the addresses + * from subnet iprim as deleted, even if they are in device list. + * In this case the secondary ifa can be in device list. + */ +void fib_del_ifaddr(struct in_ifaddr *ifa, struct in_ifaddr *iprim)  {  	struct in_device *in_dev = ifa->ifa_dev;  	struct net_device *dev = in_dev->dev;  	struct in_ifaddr *ifa1; -	struct in_ifaddr *prim = ifa; +	struct in_ifaddr *prim = ifa, *prim1 = NULL;  	__be32 brd = ifa->ifa_address | ~ifa->ifa_mask;  	__be32 any = ifa->ifa_address & ifa->ifa_mask;  #define LOCAL_OK	1 @@ -735,17 +740,26 @@ static void fib_del_ifaddr(struct in_ifaddr *ifa)  #define BRD0_OK		4  #define BRD1_OK		8  	unsigned ok = 0; +	int subnet = 0;		/* Primary network */ +	int gone = 1;		/* Address is missing */ +	int same_prefsrc = 0;	/* Another primary with same IP */ -	if (!(ifa->ifa_flags & IFA_F_SECONDARY)) -		fib_magic(RTM_DELROUTE, -			  dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, -			  any, ifa->ifa_prefixlen, prim); -	else { +	if (ifa->ifa_flags & IFA_F_SECONDARY) {  		prim = inet_ifa_byprefix(in_dev, any, ifa->ifa_mask);  		if (prim == NULL) {  			printk(KERN_WARNING "fib_del_ifaddr: bug: prim == NULL\n");  			return;  		} +		if (iprim && iprim != prim) { +			printk(KERN_WARNING "fib_del_ifaddr: bug: iprim != prim\n"); +			return; +		} +	} else if (!ipv4_is_zeronet(any) && +		   (any != ifa->ifa_local || ifa->ifa_prefixlen < 32)) { +		fib_magic(RTM_DELROUTE, +			  dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, +			  any, ifa->ifa_prefixlen, prim); +		subnet = 1;  	}  	/* Deletion is more complicated than add. @@ -755,6 +769,49 @@ static void fib_del_ifaddr(struct in_ifaddr *ifa)  	 */  	for (ifa1 = in_dev->ifa_list; ifa1; ifa1 = ifa1->ifa_next) { +		if (ifa1 == ifa) { +			/* promotion, keep the IP */ +			gone = 0; +			continue; +		} +		/* Ignore IFAs from our subnet */ +		if (iprim && ifa1->ifa_mask == iprim->ifa_mask && +		    inet_ifa_match(ifa1->ifa_address, iprim)) +			continue; + +		/* Ignore ifa1 if it uses different primary IP (prefsrc) */ +		if (ifa1->ifa_flags & IFA_F_SECONDARY) { +			/* Another address from our subnet? */ +			if (ifa1->ifa_mask == prim->ifa_mask && +			    inet_ifa_match(ifa1->ifa_address, prim)) +				prim1 = prim; +			else { +				/* We reached the secondaries, so +				 * same_prefsrc should be determined. +				 */ +				if (!same_prefsrc) +					continue; +				/* Search new prim1 if ifa1 is not +				 * using the current prim1 +				 */ +				if (!prim1 || +				    ifa1->ifa_mask != prim1->ifa_mask || +				    !inet_ifa_match(ifa1->ifa_address, prim1)) +					prim1 = inet_ifa_byprefix(in_dev, +							ifa1->ifa_address, +							ifa1->ifa_mask); +				if (!prim1) +					continue; +				if (prim1->ifa_local != prim->ifa_local) +					continue; +			} +		} else { +			if (prim->ifa_local != ifa1->ifa_local) +				continue; +			prim1 = ifa1; +			if (prim != prim1) +				same_prefsrc = 1; +		}  		if (ifa->ifa_local == ifa1->ifa_local)  			ok |= LOCAL_OK;  		if (ifa->ifa_broadcast == ifa1->ifa_broadcast) @@ -763,19 +820,37 @@ static void fib_del_ifaddr(struct in_ifaddr *ifa)  			ok |= BRD1_OK;  		if (any == ifa1->ifa_broadcast)  			ok |= BRD0_OK; +		/* primary has network specific broadcasts */ +		if (prim1 == ifa1 && ifa1->ifa_prefixlen < 31) { +			__be32 brd1 = ifa1->ifa_address | ~ifa1->ifa_mask; +			__be32 any1 = ifa1->ifa_address & ifa1->ifa_mask; + +			if (!ipv4_is_zeronet(any1)) { +				if (ifa->ifa_broadcast == brd1 || +				    ifa->ifa_broadcast == any1) +					ok |= BRD_OK; +				if (brd == brd1 || brd == any1) +					ok |= BRD1_OK; +				if (any == brd1 || any == any1) +					ok |= BRD0_OK; +			} +		}  	}  	if (!(ok & BRD_OK))  		fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim); -	if (!(ok & BRD1_OK)) -		fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim); -	if (!(ok & BRD0_OK)) -		fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim); +	if (subnet && ifa->ifa_prefixlen < 31) { +		if (!(ok & BRD1_OK)) +			fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim); +		if (!(ok & BRD0_OK)) +			fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim); +	}  	if (!(ok & LOCAL_OK)) {  		fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim);  		/* Check, that this local address finally disappeared. */ -		if (inet_addr_type(dev_net(dev), ifa->ifa_local) != RTN_LOCAL) { +		if (gone && +		    inet_addr_type(dev_net(dev), ifa->ifa_local) != RTN_LOCAL) {  			/* And the last, but not the least thing.  			 * We must flush stray FIB entries.  			 * @@ -896,7 +971,7 @@ static int fib_inetaddr_event(struct notifier_block *this, unsigned long event,  		rt_cache_flush(dev_net(dev), -1);  		break;  	case NETDEV_DOWN: -		fib_del_ifaddr(ifa); +		fib_del_ifaddr(ifa, NULL);  		fib_update_nh_saddrs(dev);  		if (ifa->ifa_dev->ifa_list == NULL) {  			/* Last address was deleted from this interface.  |