diff options
Diffstat (limited to 'net/sctp/protocol.c')
| -rw-r--r-- | net/sctp/protocol.c | 147 | 
1 files changed, 147 insertions, 0 deletions
diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index 67380a29e2e..013c6136c54 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -623,6 +623,142 @@ static void sctp_v4_ecn_capable(struct sock *sk)  	INET_ECN_xmit(sk);  } +void sctp_addr_wq_timeout_handler(unsigned long arg) +{ +	struct sctp_sockaddr_entry *addrw, *temp; +	struct sctp_sock *sp; + +	spin_lock_bh(&sctp_addr_wq_lock); + +	list_for_each_entry_safe(addrw, temp, &sctp_addr_waitq, list) { +		SCTP_DEBUG_PRINTK_IPADDR("sctp_addrwq_timo_handler: the first ent in wq %p is ", +		    " for cmd %d at entry %p\n", &sctp_addr_waitq, &addrw->a, addrw->state, +		    addrw); + +		/* Now we send an ASCONF for each association */ +		/* Note. we currently don't handle link local IPv6 addressees */ +		if (addrw->a.sa.sa_family == AF_INET6) { +			struct in6_addr *in6; + +			if (ipv6_addr_type(&addrw->a.v6.sin6_addr) & +			    IPV6_ADDR_LINKLOCAL) +				goto free_next; + +			in6 = (struct in6_addr *)&addrw->a.v6.sin6_addr; +			if (ipv6_chk_addr(&init_net, in6, NULL, 0) == 0 && +			    addrw->state == SCTP_ADDR_NEW) { +				unsigned long timeo_val; + +				SCTP_DEBUG_PRINTK("sctp_timo_handler: this is on DAD, trying %d sec later\n", +				    SCTP_ADDRESS_TICK_DELAY); +				timeo_val = jiffies; +				timeo_val += msecs_to_jiffies(SCTP_ADDRESS_TICK_DELAY); +				mod_timer(&sctp_addr_wq_timer, timeo_val); +				break; +			} +		} + +		list_for_each_entry(sp, &sctp_auto_asconf_splist, auto_asconf_list) { +			struct sock *sk; + +			sk = sctp_opt2sk(sp); +			/* ignore bound-specific endpoints */ +			if (!sctp_is_ep_boundall(sk)) +				continue; +			sctp_bh_lock_sock(sk); +			if (sctp_asconf_mgmt(sp, addrw) < 0) +				SCTP_DEBUG_PRINTK("sctp_addrwq_timo_handler: sctp_asconf_mgmt failed\n"); +			sctp_bh_unlock_sock(sk); +		} +free_next: +		list_del(&addrw->list); +		kfree(addrw); +	} +	spin_unlock_bh(&sctp_addr_wq_lock); +} + +static void sctp_free_addr_wq(void) +{ +	struct sctp_sockaddr_entry *addrw; +	struct sctp_sockaddr_entry *temp; + +	spin_lock_bh(&sctp_addr_wq_lock); +	del_timer(&sctp_addr_wq_timer); +	list_for_each_entry_safe(addrw, temp, &sctp_addr_waitq, list) { +		list_del(&addrw->list); +		kfree(addrw); +	} +	spin_unlock_bh(&sctp_addr_wq_lock); +} + +/* lookup the entry for the same address in the addr_waitq + * sctp_addr_wq MUST be locked + */ +static struct sctp_sockaddr_entry *sctp_addr_wq_lookup(struct sctp_sockaddr_entry *addr) +{ +	struct sctp_sockaddr_entry *addrw; + +	list_for_each_entry(addrw, &sctp_addr_waitq, list) { +		if (addrw->a.sa.sa_family != addr->a.sa.sa_family) +			continue; +		if (addrw->a.sa.sa_family == AF_INET) { +			if (addrw->a.v4.sin_addr.s_addr == +			    addr->a.v4.sin_addr.s_addr) +				return addrw; +		} else if (addrw->a.sa.sa_family == AF_INET6) { +			if (ipv6_addr_equal(&addrw->a.v6.sin6_addr, +			    &addr->a.v6.sin6_addr)) +				return addrw; +		} +	} +	return NULL; +} + +void sctp_addr_wq_mgmt(struct sctp_sockaddr_entry *addr, int cmd) +{ +	struct sctp_sockaddr_entry *addrw; +	unsigned long timeo_val; + +	/* first, we check if an opposite message already exist in the queue. +	 * If we found such message, it is removed. +	 * This operation is a bit stupid, but the DHCP client attaches the +	 * new address after a couple of addition and deletion of that address +	 */ + +	spin_lock_bh(&sctp_addr_wq_lock); +	/* Offsets existing events in addr_wq */ +	addrw = sctp_addr_wq_lookup(addr); +	if (addrw) { +		if (addrw->state != cmd) { +			SCTP_DEBUG_PRINTK_IPADDR("sctp_addr_wq_mgmt offsets existing entry for %d ", +			    " in wq %p\n", addrw->state, &addrw->a, +			    &sctp_addr_waitq); +			list_del(&addrw->list); +			kfree(addrw); +		} +		spin_unlock_bh(&sctp_addr_wq_lock); +		return; +	} + +	/* OK, we have to add the new address to the wait queue */ +	addrw = kmemdup(addr, sizeof(struct sctp_sockaddr_entry), GFP_ATOMIC); +	if (addrw == NULL) { +		spin_unlock_bh(&sctp_addr_wq_lock); +		return; +	} +	addrw->state = cmd; +	list_add_tail(&addrw->list, &sctp_addr_waitq); +	SCTP_DEBUG_PRINTK_IPADDR("sctp_addr_wq_mgmt add new entry for cmd:%d ", +	    " in wq %p\n", addrw->state, &addrw->a, &sctp_addr_waitq); + +	if (!timer_pending(&sctp_addr_wq_timer)) { +		timeo_val = jiffies; +		timeo_val += msecs_to_jiffies(SCTP_ADDRESS_TICK_DELAY); +		mod_timer(&sctp_addr_wq_timer, timeo_val); +	} +	spin_unlock_bh(&sctp_addr_wq_lock); +} +  /* Event handler for inet address addition/deletion events.   * The sctp_local_addr_list needs to be protocted by a spin lock since   * multiple notifiers (say IPv4 and IPv6) may be running at the same @@ -650,6 +786,7 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,  			addr->valid = 1;  			spin_lock_bh(&sctp_local_addr_lock);  			list_add_tail_rcu(&addr->list, &sctp_local_addr_list); +			sctp_addr_wq_mgmt(addr, SCTP_ADDR_NEW);  			spin_unlock_bh(&sctp_local_addr_lock);  		}  		break; @@ -660,6 +797,7 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,  			if (addr->a.sa.sa_family == AF_INET &&  					addr->a.v4.sin_addr.s_addr ==  					ifa->ifa_local) { +				sctp_addr_wq_mgmt(addr, SCTP_ADDR_DEL);  				found = 1;  				addr->valid = 0;  				list_del_rcu(&addr->list); @@ -1242,6 +1380,7 @@ SCTP_STATIC __init int sctp_init(void)  	/* Disable ADDIP by default. */  	sctp_addip_enable = 0;  	sctp_addip_noauth = 0; +	sctp_default_auto_asconf = 0;  	/* Enable PR-SCTP by default. */  	sctp_prsctp_enable = 1; @@ -1266,6 +1405,13 @@ SCTP_STATIC __init int sctp_init(void)  	spin_lock_init(&sctp_local_addr_lock);  	sctp_get_local_addr_list(); +	/* Initialize the address event list */ +	INIT_LIST_HEAD(&sctp_addr_waitq); +	INIT_LIST_HEAD(&sctp_auto_asconf_splist); +	spin_lock_init(&sctp_addr_wq_lock); +	sctp_addr_wq_timer.expires = 0; +	setup_timer(&sctp_addr_wq_timer, sctp_addr_wq_timeout_handler, 0); +  	status = sctp_v4_protosw_init();  	if (status) @@ -1337,6 +1483,7 @@ SCTP_STATIC __exit void sctp_exit(void)  	/* Unregister with inet6/inet layers. */  	sctp_v6_del_protocol();  	sctp_v4_del_protocol(); +	sctp_free_addr_wq();  	/* Free the control endpoint.  */  	inet_ctl_sock_destroy(sctp_ctl_sock);  |