This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: sockets convert out-of-range port numbers % 2**16
Type: enhancement Stage: needs patch
Components: Versions: Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Kurt.Rose, r.david.murray
Priority: normal Keywords:

Created on 2015-05-12 18:50 by Kurt.Rose, last changed 2022-04-11 14:58 by admin.

Messages (6)
msg242987 - (view) Author: Kurt Rose (Kurt.Rose) Date: 2015-05-12 18:50
This appears to affect all versions of Python.  In a behavior inherited from C, TCP ports that are > 2 bytes are silently truncated.

Here is a simple reproduction:

>>> socket.create_connection( ('google.com', 2**16 + 80) )
<socket.socket object, fd=408, family=2, type=1, proto=0>

Needs more investigation, but one likely place to make the fix is here:
https://hg.python.org/cpython/file/9d2c4d887c19/Modules/socketmodule.c#l1535

        if (!PyArg_ParseTuple(args, "O&i:getsockaddrarg",
                              idna_converter, &host, &port))

Instead of parsing port with i, use H.  This is a deep change to the behavior, but I think it is very unlikely that users intentionally mean to pass a TCP port > 2**16.  More likely, this is silently swallowing errors. 

There no indication that the passed port parameter is not being used for the actual TCP communication (which is physically impossible because TCP only has a 2 byte port field).

In fact, the socket will continue to "lie" to the user and obfuscate the actual port being used if getsockname() is called:

>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 54899)
msg242992 - (view) Author: Kurt Rose (Kurt.Rose) Date: 2015-05-12 19:12
I was incorrect -- the result of getsockname() appears to be some garbage port:

>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56446)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56447)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56448)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56449)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56450)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56451)
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
('10.225.89.86', 56452)

Java's stdlib gives a proper error message:

>>> java.net.Socket("google.com", 2**16 + 80)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
        at java.net.InetSocketAddress.<init>(Unknown Source)
        at java.net.Socket.<init>(Unknown Source)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)

        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Sou
rce)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at org.python.core.PyReflectedConstructor.constructProxy(PyReflectedCons
tructor.java:210)

java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: port out
 of range:65616


The .NET runtime also rejects invalid ports:

>>> System.Net.IPEndPoint(0x7F000001, 2**16 + 80)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Specified argument was out of the range of valid values.
Parameter name: port

IronPython by extension rejects the invalid port:

>>> socket.create_connection( ('google.com', 2**16 + 80) )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Specified argument was out of the range of valid values.
Parameter name: port

However, Jython recreates the behavior of CPython:

>>> socket.create_connection( ('google.com', 2**16 + 80) ).getsockname()
(u'10.225.89.86', 63071)
msg242999 - (view) Author: Kurt Rose (Kurt.Rose) Date: 2015-05-12 19:30
Sorry, dumb mistake on my part.  I should have been calling getpeername(), not getsockname()

In that case the result is 80:
>>> socket.create_connection( ('google.com', 2**16 + 80) ).getpeername()
('74.125.239.41', 80)

The "random" ports were the client-side ephemeral ports.
msg243002 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-05-12 19:58
You pegged it when you said the behavior is inherited from C.  Technically this isn't a bug in Python, since the socket module is a set of (mostly) thin wrappers around the C.

Enhancing CPython to do the check is not a bad suggestion, especially since it seems that other languages and implementations do so.  We won't fix this in a maintenance release, though, since it would pointlessly break working code (even if that code is technically buggy).
msg243027 - (view) Author: Kurt Rose (Kurt.Rose) Date: 2015-05-12 22:39
Totally agree this needs to be managed carefully.  My goal here was just to raise awareness and see if there is consensus that the behavior should be changed.

I came across this because an upstream process had a bug which led to impossible TCP ports being specified.  So, for my use case it would have been better if create_connection() had rejected the invalid data.

If we are talking about enhancements to the socket module, it would also be nice if errors included the address :-)
msg243109 - (view) Author: Kurt Rose (Kurt.Rose) Date: 2015-05-13 18:01
I think this may in fact be a bug.  There are other places in the socket module where port is checked, create_connection() just seems to have been missed.

create_connection() and socket.connect() have different behavior:

>>> socket.create_connection( ('google.com', 0x10000 + 80) )
<socket._socketobject object at 0x028B3F48>
>>> socket.socket().connect( ('google.com', 0x10000 + 80) )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
OverflowError: getsockaddrarg: port must be 0-65535.


https://hg.python.org/cpython/file/712f246da49b/Modules/socketmodule.c#l1395

        if (port < 0 || port > 0xffff) {
            PyErr_SetString(
                PyExc_OverflowError,
                "getsockaddrarg: port must be 0-65535.");
            return 0;
        }
History
Date User Action Args
2022-04-11 14:58:16adminsetgithub: 68357
2015-05-13 18:01:42Kurt.Rosesetmessages: + msg243109
2015-05-12 22:39:47Kurt.Rosesetmessages: + msg243027
2015-05-12 19:58:25r.david.murraysetversions: - Python 2.7, Python 3.2, Python 3.3, Python 3.4, Python 3.6
nosy: + r.david.murray

messages: + msg243002

type: behavior -> enhancement
stage: needs patch
2015-05-12 19:30:51Kurt.Rosesetmessages: + msg242999
2015-05-12 19:12:14Kurt.Rosesetmessages: + msg242992
2015-05-12 18:50:58Kurt.Rosecreate