Skip to content

Commit 8a1bc3f

Browse files
committed
Allow IP proxy connections without SNI
1 parent 9a31330 commit 8a1bc3f

4 files changed

Lines changed: 37 additions & 3 deletions

File tree

‎roles/https_proxy/templates/gost.yml.j2‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ services:
2121
tls:
2222
certFile: {{ megaproxy_container_certificate_directory }}/fullchain.pem
2323
keyFile: {{ megaproxy_container_certificate_directory }}/privkey.pem
24-
rejectUnknownSNI: true
24+
rejectUnknownSNI: {{ (not route.is_ip) | lower }}
2525
serverNames: [{{ route.hostname | to_json }}]
2626
options:
2727
minVersion: VersionTLS12

‎src/megaproxy_server/inventory.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def https_routes(inventory: Inventory, name: str) -> list[dict[str, Any]]:
137137
routes: list[dict[str, Any]] = []
138138
entry_title = https.title or _host_title(name)
139139
if https.direct:
140-
routes.append({"name": "direct", "hostname": https.endpoint, "title": entry_title, "country_code": name.split("_", 1)[0].upper(), "port": inventory.settings.https_chain_backend_port, "chain": None, "probe_resistance": https.probe_resistance.model_dump(mode="json")})
140+
routes.append({"name": "direct", "hostname": https.endpoint, "is_ip": _is_ip(https.endpoint), "title": entry_title, "country_code": name.split("_", 1)[0].upper(), "port": inventory.settings.https_chain_backend_port, "chain": None, "probe_resistance": https.probe_resistance.model_dump(mode="json")})
141141
if inventory.settings.https_chains_enabled and https.chain_entry:
142142
configured_pairs = [pair for pair in inventory.settings.https_chain_pairs if pair.entry == name]
143143
allowed_exits = {pair.exit for pair in configured_pairs} if inventory.settings.https_chain_pairs else None
@@ -153,9 +153,11 @@ def https_routes(inventory: Inventory, name: str) -> list[dict[str, Any]]:
153153
]
154154
for index, (exit_name, exit_https) in enumerate(exits, start=len(routes)):
155155
probe_resistance = pair_probe_resistance.get(exit_name) or https.probe_resistance
156+
route_hostname = pair_hostnames.get(exit_name) or f"{_dns_label(name)}-via-{_dns_label(exit_name)}.{inventory.settings.https_chain_domain}"
156157
routes.append({
157158
"name": f"via-{exit_name}",
158-
"hostname": pair_hostnames.get(exit_name) or f"{_dns_label(name)}-via-{_dns_label(exit_name)}.{inventory.settings.https_chain_domain}",
159+
"hostname": route_hostname,
160+
"is_ip": _is_ip(route_hostname),
159161
"title": pair_titles.get(exit_name) or entry_title,
160162
"country_code": pair_country_codes.get(exit_name) or exit_name.split("_", 1)[0].upper(),
161163
"port": inventory.settings.https_chain_backend_port + index,
@@ -169,6 +171,14 @@ def _dns_label(value: str) -> str:
169171
return re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")
170172

171173

174+
def _is_ip(value: str) -> bool:
175+
try:
176+
ipaddress.ip_address(value)
177+
except ValueError:
178+
return False
179+
return True
180+
181+
172182
def _host_title(name: str) -> str:
173183
parts = name.split("_")
174184
if parts[-1:] == ["proxy"]:

‎tests/test_https_proxy_role.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def test_https_proxy_uses_http2() -> None:
5656

5757
assert "type: http2" in template
5858
assert "alpn: [h2, http/1.1]" in template
59+
assert "rejectUnknownSNI: {{ (not route.is_ip) | lower }}" in template
5960

6061
verify = (ROOT / "playbooks" / "verify.yml").read_text(encoding="utf-8")
6162
assert "--proxy-http2" not in verify

‎tests/test_inventory.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def test_https_sni_routes_are_generated_for_every_exit() -> None:
102102
)
103103
routes = https_routes(inventory, "proxy_ru")
104104
assert [route["hostname"] for route in routes] == ["ru.example", "proxy-ru-via-proxy-nl.chains.example"]
105+
assert [route["is_ip"] for route in routes] == [False, False]
105106
assert [route["title"] for route in routes] == ["PROXY Ru", "Russia via Netherlands"]
106107
projected = ansible_inventory(inventory)["all"]["hosts"]["proxy_ru"]["megaproxy_services"]["https"]
107108
assert projected["routes"][1]["chain"]["host"] == "nl.example"
@@ -120,6 +121,28 @@ def test_https_sni_routes_are_generated_for_every_exit() -> None:
120121
assert ansible_inventory(inventory)["all"]["vars"]["megaproxy_users"]["https"][0]["name"] == "alice"
121122

122123

124+
def test_ip_https_route_allows_clients_without_sni() -> None:
125+
admin = AdminAccess(user="deploy", private_key_file="/keys/admin", public_key="ssh-ed25519 AAAA admin")
126+
user = HttpsUser(name="alice", password="long-password-alice")
127+
inventory = Inventory(
128+
hosts={
129+
"proxy_us": Host(
130+
address="192.0.2.1",
131+
admin=admin,
132+
services=Services(
133+
https=HttpsService(
134+
endpoint="192.0.2.1",
135+
certificate="self-signed",
136+
users=[user],
137+
)
138+
),
139+
)
140+
}
141+
)
142+
143+
assert https_routes(inventory, "proxy_us")[0]["is_ip"] is True
144+
145+
123146
def test_route_probe_override_is_independent() -> None:
124147
admin = AdminAccess(user="deploy", private_key_file="/keys/admin", public_key="ssh-ed25519 AAAA admin")
125148
user = HttpsUser(name="alice", password="long-password-alice")

0 commit comments

Comments
 (0)