|
@@ -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.
|