task #6995: Implement SO_REUSEADDR (correctly)

This commit is contained in:
goldsimon 2010-05-12 22:29:58 +00:00
parent ef0a7ecbcd
commit d0348e0c60
3 changed files with 91 additions and 62 deletions

View File

@ -13,6 +13,9 @@ HISTORY
++ New features:
2010-05-13: Simon Goldschmidt
* tcp.c, udp.c: task #6995: Implement SO_REUSEADDR (correctly)
2010-05-02: Simon Goldschmidt
* netbuf.h/.c, sockets.c, api_msg.c: use checksum-on-copy for sending
UDP data for LWIP_NETIF_TX_SINGLE_PBUF==1

View File

@ -88,6 +88,12 @@ struct tcp_pcb *tcp_active_pcbs;
/** List of all TCP PCBs in TIME-WAIT state */
struct tcp_pcb *tcp_tw_pcbs;
#define NUM_TCP_PCB_LISTS 4
#define NUM_TCP_PCB_LISTS_NO_TIME_WAIT 3
/** An array with all (non-temporary) PCB lists, mainly used for smaller code size */
struct tcp_pcb **tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs,
&tcp_active_pcbs, &tcp_tw_pcbs};
/** Only used for temporary storage. */
struct tcp_pcb *tcp_tmp_pcb;
@ -390,55 +396,46 @@ tcp_abort(struct tcp_pcb *pcb)
err_t
tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
{
int i;
int max_pcb_list = NUM_TCP_PCB_LISTS;
struct tcp_pcb *cpcb;
LWIP_ERROR("tcp_bind: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
#if SO_REUSE
/* Unless the REUSEADDR flag is set,
we have to check the pcbs in TIME-WAIT state, also.
We do not dump TIME_WAIT pcb's; they can still be matched by incoming
packets using both local and remote IP addresses and ports to distinguish.
*/
#if SO_REUSE
if ((pcb->so_options & SOF_REUSEADDR) != 0) {
max_pcb_list = NUM_TCP_PCB_LISTS_NO_TIME_WAIT;
}
#endif /* SO_REUSE */
#endif /* SO_REUSE */
if (port == 0) {
port = tcp_new_port();
}
/* Check if the address already is in use. */
/* Check the listen pcbs. */
for(cpcb = tcp_listen_pcbs.pcbs;
cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == port) {
if (ip_addr_isany(&(cpcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
return ERR_USE;
}
}
}
/* Check the connected pcbs. */
for(cpcb = tcp_active_pcbs;
cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == port) {
if (ip_addr_isany(&(cpcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
return ERR_USE;
}
}
}
/* Check the bound, not yet connected pcbs. */
for(cpcb = tcp_bound_pcbs; cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == port) {
if (ip_addr_isany(&(cpcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
return ERR_USE;
}
}
}
/* Unless the REUSEADDR flag is set,
* we have to check the pcbs in TIME-WAIT state, also: */
if ((pcb->so_options & SOF_REUSEADDR) == 0) {
for(cpcb = tcp_tw_pcbs; cpcb != NULL; cpcb = cpcb->next) {
/* Check if the address already is in use (on all lists) */
for (i = 0; i < max_pcb_list; i++) {
for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if (cpcb->local_port == port) {
if (ip_addr_isany(&(cpcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
return ERR_USE;
#if SO_REUSE
/* Omit checking for the same port if both pcbs have REUSEADDR set.
For SO_REUSEADDR, the duplicate-check for a 5-tuple is done in
tcp_connect. */
if (((pcb->so_options & SOF_REUSEADDR) == 0) ||
((cpcb->so_options & SOF_REUSEADDR) == 0))
#endif /* SO_REUSE */
{
if (ip_addr_isany(&(cpcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {
return ERR_USE;
}
}
}
}
@ -593,6 +590,7 @@ tcp_recved(struct tcp_pcb *pcb, u16_t len)
static u16_t
tcp_new_port(void)
{
int i;
struct tcp_pcb *pcb;
#ifndef TCP_LOCAL_PORT_RANGE_START
#define TCP_LOCAL_PORT_RANGE_START 4096
@ -604,20 +602,12 @@ tcp_new_port(void)
if (++port > TCP_LOCAL_PORT_RANGE_END) {
port = TCP_LOCAL_PORT_RANGE_START;
}
for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->local_port == port) {
goto again;
}
}
for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->local_port == port) {
goto again;
}
}
for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->local_port == port) {
goto again;
/* Check all PCB lists. */
for (i = 1; i < NUM_TCP_PCB_LISTS; i++) {
for(pcb = *tcp_pcb_lists[i]; pcb != NULL; pcb = pcb->next) {
if (pcb->local_port == port) {
goto again;
}
}
}
return port;
@ -651,9 +641,43 @@ tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port,
return ERR_VAL;
}
pcb->remote_port = port;
/* check if we have a route to the remote host */
if (ip_addr_isany(&(pcb->local_ip))) {
/* no local IP address set, yet. */
struct netif *netif = ip_route(&(pcb->remote_ip));
if (netif == NULL) {
/* Don't even try to send a SYN packet if we have not route
since that will fail. */
return ERR_RTE;
}
/* Use the netif's IP address as local address. */
ip_addr_copy(pcb->local_ip, netif->ip_addr);
}
if (pcb->local_port == 0) {
pcb->local_port = tcp_new_port();
}
#if SO_REUSE
if ((pcb->so_options & SOF_REUSEADDR) != 0) {
/* Since SOF_REUSEADDR allows reusing a local address, we have to make sure
now that the 5-tuple is unique. */
struct tcp_pcb *cpcb;
int i;
/* Don't check listen PCBs, check bound-, active- and TIME-WAIT PCBs. */
for (i = 1; i < NUM_TCP_PCB_LISTS; i++) {
for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
if ((cpcb->local_port == pcb->local_port) &&
(cpcb->remote_port == pcb->remote_port) &&
ip_addr_cmp(&cpcb->local_ip, &pcb->local_ip) &&
ip_addr_cmp(&cpcb->local_ip, &pcb->local_ip)) {
/* linux returns EISCONN here, but ERR_USE should be OK for us */
return ERR_USE;
}
}
}
}
#endif /* SO_REUSE */
iss = tcp_next_iss();
pcb->rcv_nxt = 0;
pcb->snd_nxt = iss;

View File

@ -660,13 +660,16 @@ udp_bind(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
rebind = 1;
}
/* this code does not allow upper layer to share a UDP port for
listening to broadcast or multicast traffic (See SO_REUSEADDR and
SO_REUSEPORT under *BSD). TODO: See where it fits instead, OR
combine with implementation of UDP PCB flags. Leon Woestenberg. */
#ifdef LWIP_UDP_TODO
/* port matches that of PCB in list? */
/* By default, we don't allow to bind to a port that any other udp
PCB is alread bound to, unless *all* PCBs with that port have tha
REUSEADDR flag set. */
#if SO_REUSE
else if (((pcb->so_options & SOF_REUSEADDR) == 0) &&
((ipcb->so_options & SOF_REUSEADDR) == 0)) {
#else /* SO_REUSE */
/* port matches that of PCB in list and REUSEADDR not set -> reject */
else {
#endif /* SO_REUSE */
if ((ipcb->local_port == port) &&
/* IP address matches, or one is IP_ADDR_ANY? */
(ip_addr_isany(&(ipcb->local_ip)) ||
@ -678,7 +681,6 @@ udp_bind(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
return ERR_USE;
}
}
#endif
}
ip_addr_set(&pcb->local_ip, ipaddr);