Reimplement IFF_DYNAMIC flag This dates back to an patch that used to be forever in the SUSE Kernel. ifconfig also still supports it. When a interface has the IFF_DYNAMIC set and its IP address goes away reset all TCP sockets referencing it. This allows the programs to quickly reestablish connections when the IP address changes -- no more hanging connections. This is for example useful to discovery quickly from the "24h DSL timeout from hell" many German DSL connections are suffering from. The old iff-dynamic patch used to trigger this on ISDN hangups; this one does it a little differently by adding IFA_F_DYNAMIC to an IP address and killing sockets when it goes away. This way it works for any kind of interface; not just ISDN. Signed-off-by: Andi Kleen --- include/linux/notifier.h | 1 include/net/tcp.h | 1 net/core/dev.c | 4 +- net/ipv4/devinet.c | 21 +++++++++++ net/ipv4/tcp_input.c | 2 - net/ipv4/tcp_ipv4.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 2 deletions(-) Index: linux/net/ipv4/devinet.c =================================================================== --- linux.orig/net/ipv4/devinet.c +++ linux/net/ipv4/devinet.c @@ -377,6 +377,9 @@ static int __inet_insert_ifa(struct in_i ifap = last_primary; } + if (in_dev->dev->flags & IFF_DYNAMIC) + ifa->ifa_flags |= IFA_F_DYNAMIC; + ifa->ifa_next = *ifap; *ifap = ifa; @@ -1043,6 +1046,23 @@ static void inetdev_changename(struct ne } } +static void +inetdev_flags_change(struct net_device *dev, struct in_device *in_dev) +{ + for_ifa (in_dev) { + unsigned of = ifa->ifa_flags & IFA_F_DYNAMIC; + if (dev->flags & IFF_DYNAMIC) + ifa->ifa_flags |= IFA_F_DYNAMIC; + else + ifa->ifa_flags &= ~IFA_F_DYNAMIC; + if (of != (ifa->ifa_flags & IFA_F_DYNAMIC)) + printk("%s dynamic on %u.%u.%u.%u\n", + (ifa->ifa_flags & IFA_F_DYNAMIC) ? "enabling" : "disabling", + NIPQUAD(ifa->ifa_local)); + + } endfor_ifa (in_dev); +} + /* Called only under RTNL semaphore */ static int inetdev_event(struct notifier_block *this, unsigned long event, @@ -1114,6 +1134,9 @@ static int inetdev_event(struct notifier devinet_sysctl_register(in_dev, &in_dev->cnf); #endif break; + case NETDEV_FLAGS_CHANGE: + inetdev_flags_change(dev, in_dev); + break; } out: return NOTIFY_DONE; Index: linux/net/ipv4/tcp_ipv4.c =================================================================== --- linux.orig/net/ipv4/tcp_ipv4.c +++ linux/net/ipv4/tcp_ipv4.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include @@ -2411,6 +2412,89 @@ void tcp4_proc_exit(void) } #endif /* CONFIG_PROC_FS */ +/* + * Quickly reset sockets when their local dynamic IP address goes away. + */ +static void tcp_v4_zap_addr(__u32 addr) +{ + int i; + + for (i = 0; i < tcp_hashinfo.ehash_size; i++) { + struct inet_ehash_bucket *head = &tcp_hashinfo.ehash[i]; + struct sock *sk; + struct hlist_node *node; + + /* O(n^2) on number of zapped sockets in a single hash chain. + Hopefully there is only a small number of them. + Should be only a problem if the hash function fails + which is unlikely (or rather if this happens we have + much more serious problems than this) + It would be possible to avoid this by inserting a marker + into the list or open coding unhash here, but I don't see + a need for this right now. -AK */ + again: + read_lock(&head->lock); + sk_for_each(sk, node, &head->chain) { + struct inet_sock *inet = inet_sk(sk); + + if (sk->sk_family != AF_INET) + continue; + if (inet->rcv_saddr != addr) + continue; + if (sock_flag(sk, SOCK_DEAD) || sk->sk_err) + continue; + if (sk->sk_state == TCP_SYN_RECV) + continue; + if (sk->sk_state == TCP_SYN_SENT && sysctl_ip_dynaddr) + continue; + printk(KERN_INFO + "TCP: zapping lost address %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", + NIPQUAD(addr), ntohs(inet->sport), + NIPQUAD(inet->daddr), ntohs(inet->dport)); + sock_hold(sk); + read_unlock(&head->lock); + lock_sock(sk); + tcp_reset(sk); + release_sock(sk); + sock_put(sk); + cond_resched(); + goto again; + } + read_unlock(&head->lock); + cond_resched(); + } +} + +static int tcp_v4_inetaddr_event(struct notifier_block *this, + unsigned long event, void *data) +{ + struct in_ifaddr *ifa = (struct in_ifaddr *)data; + struct net_device *dev; + +#if 0 + printk("tcpv4 inetaddr %lx %u.%u.%u.%u %s\n", event, NIPQUAD(ifa->ifa_local), + (ifa->ifa_flags & IFA_F_DYNAMIC)?"dynamic":"-"); +#endif + + if (event != NETDEV_DOWN || !(ifa->ifa_flags & IFA_F_DYNAMIC)) + return NOTIFY_DONE; + /* + * Don't do anything when the address still exists on another + * interface. Depends on running after the IP notifier. + */ + if ((dev = ip_dev_find(ifa->ifa_local)) != NULL) { + printk("IP still on other interface %s\n", dev->name); + dev_put(dev); + } else + tcp_v4_zap_addr(ifa->ifa_local); + return NOTIFY_DONE; +} + +static struct notifier_block tcp_v4_inetaddr_notifier = { + .notifier_call = tcp_v4_inetaddr_event, + .priority = -1, +}; + struct proto tcp_prot = { .name = "TCP", .owner = THIS_MODULE, @@ -2452,6 +2536,8 @@ void __init tcp_v4_init(struct net_proto if (inet_csk_ctl_sock_create(&tcp_socket, PF_INET, SOCK_RAW, IPPROTO_TCP) < 0) panic("Failed to create the TCP control socket.\n"); + + register_inetaddr_notifier(&tcp_v4_inetaddr_notifier); } EXPORT_SYMBOL(ipv4_specific); Index: linux/include/linux/notifier.h =================================================================== --- linux.orig/include/linux/notifier.h +++ linux/include/linux/notifier.h @@ -193,6 +193,7 @@ static inline int notifier_to_errno(int #define NETDEV_GOING_DOWN 0x0009 #define NETDEV_CHANGENAME 0x000A #define NETDEV_FEAT_CHANGE 0x000B +#define NETDEV_FLAGS_CHANGE 0x000C #define SYS_DOWN 0x0001 /* Notify of system down */ #define SYS_RESTART SYS_DOWN Index: linux/net/core/dev.c =================================================================== --- linux.orig/net/core/dev.c +++ linux/net/core/dev.c @@ -2927,8 +2927,10 @@ int dev_change_flags(struct net_device * /* Exclude state transition flags, already notified */ changes = (old_flags ^ dev->flags) & ~(IFF_UP | IFF_RUNNING); - if (changes) + if (changes) { rtmsg_ifinfo(RTM_NEWLINK, dev, changes); + raw_notifier_call_chain(&netdev_chain, NETDEV_FLAGS_CHANGE, dev); + } return ret; } Index: linux/include/net/tcp.h =================================================================== --- linux.orig/include/net/tcp.h +++ linux/include/net/tcp.h @@ -447,6 +447,7 @@ extern void tcp_send_delayed_ack(struct /* tcp_input.c */ extern void tcp_cwnd_application_limited(struct sock *sk); +extern void tcp_reset(struct sock *sk); /* tcp_timer.c */ extern void tcp_init_xmit_timers(struct sock *); Index: linux/net/ipv4/tcp_input.c =================================================================== --- linux.orig/net/ipv4/tcp_input.c +++ linux/net/ipv4/tcp_input.c @@ -3115,7 +3115,7 @@ static inline int tcp_sequence(struct tc } /* When we get a reset we do this. */ -static void tcp_reset(struct sock *sk) +void tcp_reset(struct sock *sk) { /* We want the right error as BSD sees it (and indeed as we do). */ switch (sk->sk_state) { Index: linux/include/linux/if_addr.h =================================================================== --- linux.orig/include/linux/if_addr.h +++ linux/include/linux/if_addr.h @@ -44,6 +44,7 @@ enum #define IFA_F_DEPRECATED 0x20 #define IFA_F_TENTATIVE 0x40 #define IFA_F_PERMANENT 0x80 +#define IFA_F_DYNAMIC 0x100 struct ifa_cacheinfo {