Jason R. Coombs <jar...@jaraco.com> added the comment:

First, a quick primer in IP:

- Addresses are written as XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX, but any 
single span of zeros can be written as ::, so `::` is all zeros and `::1` is 
the same as 0000:0000:0000:0000:0000:0000:0000:0001.
- ::1 is the local host (the some machine as where the code is running), 
equivalent to 127.0.0.1 in IPv4.
- To listen on all interfaces, the socket library expects the system to bind to 
0.0.0.0 (IPv4) or :: (IPv6).
- When specified in a URL, an IPv6 address must be wrapped in [] to distinguish 
the `:` characters from the port separator. For example, http://[::1]:8000/ 
specifies connect to localhost over IPv6 on port 8000.
- If the system supports dual-stack IPv4 over IPv6, all IPv4 addresses are 
mapped to a specific IPv6 subnet, so binding/listening on IPv6 often allows a 
client to connect to IPv4.
- Even if the server is listening on all interfaces (0.0.0.0/::), the client 
must specify an internet address that will reach that address.

As a result of this last point, it's not possible for a server like http.server 
to reliably know what address a client would be able to use to connect to the 
server. That is, if the server is bound on all interfaces, a local client could 
connect over localhost/127.0.0.1/::1 (assuming that interface exist, which it 
doesn't sometimes) or to another address assigned  by the host, e.g. 
2601:547:501:6ba:d1e6:300d:7e83:6b6f. A client on another host, however, would 
not be able to use localhost to connect to the server. It _must_ use an address 
that's both assigned to the server's host, bound by the server, and routeable 
to/from the client (i.e. not blocked by a firewall).

Prior to Python 3.8, the default behavior was to bind to all interfaces on IPv4 
only, which was unnecessarily limiting, but was subject to the same unexpected 
behavior:

```
draft $ python3.7 -m http.server                                                
                                                                                
                                             
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
```

The URL there `http://0.0.0.0:8000/` has the same failure mode as the one 
described above. One cannot browse to that address, but must replace `0.0.0.0` 
with `localhost` or `127.0.0.1` (to connect from localhost) or replace with a 
routable address to connect from another host. The only difference is that with 
Python 3.8, now IPv6 is honored.

Note if one passes `localhost` or `127.0.0.1` or `::1` as the bind parameter, 
the URL indicated would work:

```
draft $ python -m http.server --bind localhost                                  
                                                                                
                                             
Serving HTTP on ::1 port 8000 (http://[::1]:8000/) ...
```

Since it's not possible in general to supply the URL a client would need to 
connect to the server, it's difficult to reliably provide a useful URL.

Some web servers do apply [a 
heuristic](https://github.com/jaraco/portend/blob/754c37046d86d178d20faa8dbfe910482d79bdff/portend.py#L27-L46)
 that translates "all addresses" to a "localhost" address, and Python stdlib 
could implement that heuristic.

> On 3.7.X I was able to use it as described in the docs and it would default 
> to whatever IP address was available.

That behavior should be the same, except that it should now bind to both IPv6 
and IPv4. If you previously ran without any parameters, it would bind to all 
interfaces on IPv4. Now it binds on all interfaces on IPv6, which should be 
backward compatible in dual-stack environments like Windows. You just have to 
translate `[::]` to `localhost` instead of translating `0.0.0.0` to `localhost`.


When I tested your findings on macOS, everything worked as I expected. I 
launched the server with `python -m http.server`, and the site could be reached 
on http://localhost:8000/ and http://127.0.0.1:8000 and http://[::1]:8000/. 
Nevertheless, when I tried the same thing on my Windows machine, I got a 
different outcome. The server bound to [::0] but was unreachable on 
http://127.0.0.1:8000.

That was unexpected, and I'll try to ascertain why the dual-stack behavior 
isn't working as I'd expect.

----------
nosy:  -SilentGhost
stage: needs patch -> 

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue39211>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to