Skip to content

Commit 30574fd

Browse files
Support --hostnames (#1325)
* Support --hostnames * Support python binaries that are not called "python" * Update log statement to use self.hostname now --------- Co-authored-by: Abhinav Singh <[email protected]>
1 parent ac4d5a7 commit 30574fd

22 files changed

+206
-149
lines changed

Makefile

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
SHELL := /bin/bash
2+
PYTHON ?= python
23

34
NS ?= abhinavsingh
45
IMAGE_NAME ?= proxy.py
@@ -40,23 +41,23 @@ all: lib-test
4041

4142
https-certificates:
4243
# Generate server key
43-
python -m proxy.common.pki gen_private_key \
44+
$(PYTHON) -m proxy.common.pki gen_private_key \
4445
--private-key-path $(HTTPS_KEY_FILE_PATH)
45-
python -m proxy.common.pki remove_passphrase \
46+
$(PYTHON) -m proxy.common.pki remove_passphrase \
4647
--private-key-path $(HTTPS_KEY_FILE_PATH)
4748
# Generate server certificate
48-
python -m proxy.common.pki gen_public_key \
49+
$(PYTHON) -m proxy.common.pki gen_public_key \
4950
--private-key-path $(HTTPS_KEY_FILE_PATH) \
5051
--public-key-path $(HTTPS_CERT_FILE_PATH)
5152

5253
sign-https-certificates:
5354
# Generate CSR request
54-
python -m proxy.common.pki gen_csr \
55+
$(PYTHON) -m proxy.common.pki gen_csr \
5556
--csr-path $(HTTPS_CSR_FILE_PATH) \
5657
--private-key-path $(HTTPS_KEY_FILE_PATH) \
5758
--public-key-path $(HTTPS_CERT_FILE_PATH)
5859
# Sign CSR with CA
59-
python -m proxy.common.pki sign_csr \
60+
$(PYTHON) -m proxy.common.pki sign_csr \
6061
--csr-path $(HTTPS_CSR_FILE_PATH) \
6162
--crt-path $(HTTPS_SIGNED_CERT_FILE_PATH) \
6263
--hostname localhost \
@@ -65,23 +66,23 @@ sign-https-certificates:
6566

6667
ca-certificates:
6768
# Generate CA key
68-
python -m proxy.common.pki gen_private_key \
69+
$(PYTHON) -m proxy.common.pki gen_private_key \
6970
--private-key-path $(CA_KEY_FILE_PATH)
70-
python -m proxy.common.pki remove_passphrase \
71+
$(PYTHON) -m proxy.common.pki remove_passphrase \
7172
--private-key-path $(CA_KEY_FILE_PATH)
7273
# Generate CA certificate
73-
python -m proxy.common.pki gen_public_key \
74+
$(PYTHON) -m proxy.common.pki gen_public_key \
7475
--private-key-path $(CA_KEY_FILE_PATH) \
7576
--public-key-path $(CA_CERT_FILE_PATH)
7677
# Generate key that will be used to generate domain certificates on the fly
7778
# Generated certificates are then signed with CA certificate / key generated above
78-
python -m proxy.common.pki gen_private_key \
79+
$(PYTHON) -m proxy.common.pki gen_private_key \
7980
--private-key-path $(CA_SIGNING_KEY_FILE_PATH)
80-
python -m proxy.common.pki remove_passphrase \
81+
$(PYTHON) -m proxy.common.pki remove_passphrase \
8182
--private-key-path $(CA_SIGNING_KEY_FILE_PATH)
8283

8384
lib-check:
84-
python check.py
85+
$(PYTHON) check.py
8586

8687
lib-clean:
8788
find . -name '*.pyc' -exec rm -f {} +
@@ -107,10 +108,10 @@ lib-dep:
107108
pip install "setuptools>=42"
108109

109110
lib-pre-commit:
110-
python -m pre_commit run --hook-stage manual --all-files -v
111+
$(PYTHON) -m pre_commit run --hook-stage manual --all-files -v
111112

112113
lib-lint:
113-
python -m tox -e lint
114+
$(PYTHON) -m tox -e lint
114115

115116
lib-flake8:
116117
tox -e lint -- flake8 --all-files
@@ -119,12 +120,12 @@ lib-mypy:
119120
tox -e lint -- mypy --all-files
120121

121122
lib-pytest:
122-
python -m tox -e python -- -v
123+
$(PYTHON) -m tox -e python -- -v
123124

124125
lib-test: lib-clean lib-check lib-lint lib-pytest
125126

126127
lib-package: lib-clean lib-check
127-
python -m tox -e cleanup-dists,build-dists,metadata-validation
128+
$(PYTHON) -m tox -e cleanup-dists,build-dists,metadata-validation
128129

129130
lib-release-test: lib-package
130131
twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
@@ -133,7 +134,7 @@ lib-release: lib-package
133134
twine upload dist/*
134135

135136
lib-doc:
136-
python -m tox -e build-docs && \
137+
$(PYTHON) -m tox -e build-docs && \
137138
$(OPEN) .tox/build-docs/docs_out/index.html || true
138139

139140
lib-coverage: lib-clean
@@ -145,7 +146,7 @@ lib-profile:
145146
sudo py-spy record \
146147
-o profile.svg \
147148
-t -F -s -- \
148-
python -m proxy \
149+
$(PYTHON) -m proxy \
149150
--hostname 127.0.0.1 \
150151
--num-acceptors 1 \
151152
--num-workers 1 \
@@ -161,7 +162,7 @@ lib-speedscope:
161162
-o profile.speedscope.json \
162163
-f speedscope \
163164
-t -F -s -- \
164-
python -m proxy \
165+
$(PYTHON) -m proxy \
165166
--hostname 127.0.0.1 \
166167
--num-acceptors 1 \
167168
--num-workers 1 \

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@
213213
- `--enable-reverse-proxy --plugins proxy.plugin.ReverseProxyPlugin`
214214
- Plugin API is currently in *development phase*. Expect breaking changes. See [Deploying proxy.py in production](#deploying-proxypy-in-production) on how to ensure reliability across code changes.
215215

216-
- Can listen on multiple ports
216+
- Can listen on multiple addresses and ports
217+
- Use `--hostnames` flag to provide additional addresses
217218
- Use `--ports` flag to provide additional ports
218219
- Optionally, use `--port` flag to override default port `8899`
219220
- Capable of serving multiple protocols over the same port
@@ -2335,8 +2336,9 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
23352336
[--tunnel-remote-port TUNNEL_REMOTE_PORT] [--threadless]
23362337
[--threaded] [--num-workers NUM_WORKERS] [--enable-events]
23372338
[--local-executor LOCAL_EXECUTOR] [--backlog BACKLOG]
2338-
[--hostname HOSTNAME] [--port PORT] [--ports PORTS [PORTS ...]]
2339-
[--port-file PORT_FILE] [--unix-socket-path UNIX_SOCKET_PATH]
2339+
[--hostname HOSTNAME] [--hostnames HOSTNAMES [HOSTNAMES ...]]
2340+
[--port PORT] [--ports PORTS [PORTS ...]] [--port-file PORT_FILE]
2341+
[--unix-socket-path UNIX_SOCKET_PATH]
23402342
[--num-acceptors NUM_ACCEPTORS] [--version] [--log-level LOG_LEVEL]
23412343
[--log-file LOG_FILE] [--log-format LOG_FORMAT]
23422344
[--open-file-limit OPEN_FILE_LIMIT]
@@ -2405,6 +2407,8 @@ options:
24052407
--backlog BACKLOG Default: 100. Maximum number of pending connections to
24062408
proxy server.
24072409
--hostname HOSTNAME Default: 127.0.0.1. Server IP address.
2410+
--hostnames HOSTNAMES [HOSTNAMES ...]
2411+
Default: None. Additional IP addresses to listen on.
24082412
--port PORT Default: 8899. Server port. To listen on more ports,
24092413
pass them using --ports flag.
24102414
--ports PORTS [PORTS ...]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support `--hostnames` to specify multiple IP addresses to listen on.

proxy/common/flag.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import os
1212
import sys
1313
import base64
14-
import socket
1514
import argparse
1615
import ipaddress
1716
import itertools
@@ -25,7 +24,7 @@
2524
from .plugins import Plugins
2625
from .version import __version__
2726
from .constants import (
28-
COMMA, IS_WINDOWS, PLUGIN_PAC_FILE, PLUGIN_DASHBOARD, PLUGIN_HTTP_PROXY,
27+
COMMA, PLUGIN_PAC_FILE, PLUGIN_DASHBOARD, PLUGIN_HTTP_PROXY,
2928
PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER, DEFAULT_NUM_WORKERS,
3029
PLUGIN_REVERSE_PROXY, DEFAULT_NUM_ACCEPTORS, PLUGIN_INSPECT_TRAFFIC,
3130
DEFAULT_DISABLE_HEADERS, PY2_DEPRECATION_MESSAGE, DEFAULT_DEVTOOLS_WS_PATH,
@@ -291,24 +290,15 @@ def initialize(
291290
IpAddress,
292291
opts.get('hostname', ipaddress.ip_address(args.hostname)),
293292
)
293+
hostnames: List[List[str]] = opts.get('hostnames', args.hostnames)
294+
args.hostnames = [
295+
ipaddress.ip_address(hostname) for hostname in list(
296+
itertools.chain.from_iterable([] if hostnames is None else hostnames),
297+
)
298+
]
294299
args.unix_socket_path = opts.get(
295300
'unix_socket_path', args.unix_socket_path,
296301
)
297-
# AF_UNIX is not available on Windows
298-
# See https://bugs.python.org/issue33408
299-
if not IS_WINDOWS:
300-
args.family = socket.AF_UNIX if args.unix_socket_path else (
301-
socket.AF_INET6 if args.hostname.version == 6 else socket.AF_INET
302-
)
303-
else:
304-
# FIXME: Not true for tests, as this value will be a mock.
305-
#
306-
# It's a problem only on Windows. Instead of a proper
307-
# fix in the tests, simply commenting this line of assertion
308-
# for now.
309-
#
310-
# assert args.unix_socket_path is None
311-
args.family = socket.AF_INET6 if args.hostname.version == 6 else socket.AF_INET
312302
args.port = cast(int, opts.get('port', args.port))
313303
ports: List[List[int]] = opts.get('ports', args.ports)
314304
args.ports = [

proxy/core/acceptor/acceptor.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,8 @@ def _recv_and_setup_socks(self) -> None:
186186
# dynamically accept from new fds.
187187
for _ in range(self.fd_queue.recv()):
188188
fileno = recv_handle(self.fd_queue)
189-
# TODO: Convert to socks i.e. list of fds
190-
self.socks[fileno] = socket.fromfd(
191-
fileno,
192-
family=self.flags.family,
193-
type=socket.SOCK_STREAM,
194-
)
189+
sock = socket.socket(fileno=socket.dup(fileno)) # type: ignore[attr-defined]
190+
self.socks[fileno] = sock
195191
self.fd_queue.close()
196192

197193
def _start_local(self) -> None:

proxy/core/listener/pool.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111
import argparse
12+
import itertools
1213
from typing import TYPE_CHECKING, Any, List, Type
1314

1415
from .tcp import TcpSocketListener
@@ -37,10 +38,12 @@ def __exit__(self, *args: Any) -> None:
3738
def setup(self) -> None:
3839
if self.flags.unix_socket_path:
3940
self.add(UnixSocketListener)
40-
else:
41-
self.add(TcpSocketListener)
42-
for port in self.flags.ports:
43-
self.add(TcpSocketListener, port=port)
41+
hostnames = {self.flags.hostname, *self.flags.hostnames}
42+
ports = set(self.flags.ports)
43+
if not self.flags.unix_socket_path:
44+
ports.add(self.flags.port)
45+
for hostname, port in itertools.product(hostnames, ports):
46+
self.add(TcpSocketListener, hostname=hostname, port=port)
4447

4548
def shutdown(self) -> None:
4649
for listener in self.pool:

proxy/core/listener/tcp.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"""
1111
import socket
1212
import logging
13-
from typing import Any, Optional
13+
import ipaddress
14+
from typing import Any, Union, Optional
1415

1516
from .base import BaseListener
1617
from ...common.flag import flags
@@ -26,6 +27,15 @@
2627
help='Default: 127.0.0.1. Server IP address.',
2728
)
2829

30+
flags.add_argument(
31+
'--hostnames',
32+
action='append',
33+
nargs='+',
34+
type=str,
35+
default=None,
36+
help='Default: None. Additional IP addresses to listen on.',
37+
)
38+
2939
flags.add_argument(
3040
'--port',
3141
type=int,
@@ -37,6 +47,7 @@
3747
'--ports',
3848
action='append',
3949
nargs='+',
50+
type=int,
4051
default=None,
4152
help='Default: None. Additional ports to listen on.',
4253
)
@@ -54,9 +65,14 @@
5465
class TcpSocketListener(BaseListener):
5566
"""Tcp listener."""
5667

57-
def __init__(self, *args: Any, port: Optional[int] = None, **kwargs: Any) -> None:
58-
# Port if passed will be used, otherwise
59-
# flag port value will be used.
68+
def __init__(
69+
self,
70+
hostname: Union[ipaddress.IPv4Address, ipaddress.IPv6Address],
71+
port: int,
72+
*args: Any,
73+
**kwargs: Any,
74+
) -> None:
75+
self.hostname = hostname
6076
self.port = port
6177
# Set after binding to a port.
6278
#
@@ -66,19 +82,18 @@ def __init__(self, *args: Any, port: Optional[int] = None, **kwargs: Any) -> Non
6682

6783
def listen(self) -> socket.socket:
6884
sock = socket.socket(
69-
socket.AF_INET6 if self.flags.hostname.version == 6 else socket.AF_INET,
85+
socket.AF_INET6 if self.hostname.version == 6 else socket.AF_INET,
7086
socket.SOCK_STREAM,
7187
)
7288
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
7389
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
7490
# s.setsockopt(socket.SOL_TCP, socket.TCP_FASTOPEN, 5)
75-
port = self.port if self.port is not None else self.flags.port
76-
sock.bind((str(self.flags.hostname), port))
91+
sock.bind((str(self.hostname), self.port))
7792
sock.listen(self.flags.backlog)
7893
sock.setblocking(False)
7994
self._port = sock.getsockname()[1]
8095
logger.info(
8196
'Listening on %s:%s' %
82-
(self.flags.hostname, self._port),
97+
(self.hostname, self._port),
8398
)
8499
return sock

proxy/core/work/fd/fd.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ def work(self, *args: Any) -> None:
3030
fileno: int = args[0]
3131
addr: Optional[HostPort] = args[1]
3232
conn: Optional[TcpOrTlsSocket] = args[2]
33-
conn = conn or socket.fromfd(
34-
fileno, family=socket.AF_INET if self.flags.hostname.version == 4 else socket.AF_INET6,
35-
type=socket.SOCK_STREAM,
36-
)
33+
conn = conn or socket.socket(fileno=socket.dup(fileno)) # type: ignore[attr-defined]
3734
uid = '%s-%s-%s' % (self.iid, self._total, fileno)
3835
self.works[fileno] = self.create(uid, conn, addr)
3936
self.works[fileno].publish_event(

proxy/proxy.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,18 @@ def setup(self) -> None:
211211
)._port
212212
# --ports flag can also use 0 as value for ephemeral port selection.
213213
# Here, we override flags.ports to reflect actual listening ports.
214-
ports = []
215-
offset = 1 if self.flags.unix_socket_path or self.flags.port else 0
214+
ports = set()
215+
offset = 1 if self.flags.unix_socket_path else 0
216216
for index in range(offset, offset + len(self.flags.ports)):
217-
ports.append(
217+
ports.add(
218218
cast(
219219
'TcpSocketListener',
220220
self.listeners.pool[index],
221221
)._port,
222222
)
223-
self.flags.ports = ports
223+
if self.flags.port in ports:
224+
ports.remove(self.flags.port)
225+
self.flags.ports = list(ports)
224226
# Write ports to port file
225227
self._write_port_file()
226228
# Setup EventManager

0 commit comments

Comments
 (0)