From: Flavio Leitner <fbl@redhat.com>
To: netdev <netdev@vger.kernel.org>
Cc: Marcelo Leitner <mleitner@redhat.com>
Subject: bind()/inet_csk_get_port() fails when no port is requested
Date: Wed, 18 Jan 2012 15:11:26 -0200 [thread overview]
Message-ID: <20120118151126.01a74dc5@asterix.rh> (raw)
Hi folks,
It has been reported to me that bind() fails when you leave
the port up to the kernel to choose and succeed when you
request a certain port in the same conditions.
For example, let's restrict the ephemeral port range to 3 ports only:
# echo "32768 32770" > /proc/sys/net/ipv4/ip_local_port_range
Assuming the system has two IP addresses: 172.31.1.6/24 and
192.168.100.6/24 then run the following python script which
allocates all ephemeral ports using one IP address and then
try to bind another one using another IP address.
#!/usr/bin/python
import socket
ip1 = []
s = None
for i in [ 1, 2, 3, 4, 5, 6 ]:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
try:
s.bind(('172.31.1.7', 0))
ip1.append(s)
except socket.error, err: # socket.error: (98, 'Address already in use')
if err.args[0] == 98:
break
else:
raise
print '%d sockets bound at 172.31.1.7' % len(ip1)
print 'Now binding at 192.168.100.6'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.bind(('192.168.100.6', 0))
This is the result:
# ./ephemeral.py
3 sockets bound at 172.31.1.6
Now binding at 192.168.100.6
Traceback (most recent call last):
File "./ephemeral.py", line 23, in <module>
s.bind(('192.168.100.6', 0))
File "/usr/lib64/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 98] Address already in use
The last bind() fails even using a different IP address.
Now if we change the reproducer to use fixed port number instead:
#!/usr/bin/python
import socket
ip1 = []
s = None
first_port=32768
port=first_port
for i in [ 1, 2, 3, 4, 5, 6 ]:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
try:
s.bind(('172.31.1.7', port))
ip1.append(s)
except socket.error, err: # socket.error: (98, 'Address already in use')
if err.args[0] == 98:
break
else:
raise
port = port + 1
print '%d sockets bound at 172.31.1.7' % len(ip1)
print 'Now binding at 192.168.100.6'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.bind(('192.168.100.6', first_port))
This is the result:
# ./fixedports.py
6 sockets bound at 172.31.1.7
Now binding at 192.168.100.6 <-- works out!
Conclusion: When using ephemeral ports, inet_csk_get_port()
fails without checking if a conflict had happened. When using
fixed ports on the other hand, inet_csk_get_port() works
as expected.
I will attach a quick hack to illustrate what I am thinking.
The idea is to check all ports first and if it fails, then
try again looking for a port that doesn't conflict. So, for
most cases, the algorithm is the same, but when the system
ran out of ports, there is a hope :-)
Is there a reason to behave like that? or is this a real bug?
Sounds like a FAQ, but I am not finding an explanation for this
on the net yet.
*hack*
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 2e4e244..2911f06 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -97,7 +97,9 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum)
int ret, attempts = 5;
struct net *net = sock_net(sk);
int smallest_size = -1, smallest_rover;
+ bool check_conflict;
+ check_conflict = false;
local_bh_disable();
if (!snum) {
int remaining, rover, low, high;
@@ -128,6 +130,13 @@ again:
goto have_snum;
}
}
+
+ if (check_conflict && !inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb)) {
+ spin_unlock(&head->lock);
+ snum = rover;
+ goto have_snum;
+ }
+
goto next;
}
break;
@@ -150,6 +159,11 @@ again:
snum = smallest_rover;
goto have_snum;
}
+ /* try again checking if a port can be reused */
+ if (!check_conflict) {
+ check_conflict = true;
+ goto again;
+ }
goto fail;
}
/* OK, here is the one we will use. HEAD is
thanks,
fbl
next reply other threads:[~2012-01-18 17:11 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-01-18 17:11 Flavio Leitner [this message]
2012-01-18 17:36 ` bind()/inet_csk_get_port() fails when no port is requested Eric Dumazet
2012-01-18 17:50 ` Eric Dumazet
2012-01-18 17:57 ` Flavio Leitner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20120118151126.01a74dc5@asterix.rh \
--to=fbl@redhat.com \
--cc=mleitner@redhat.com \
--cc=netdev@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.