feat: per-network proxy override, CERT ADD timing fix
config: add optional proxy_host/proxy_port to NetworkConfig router: resolve per-network proxy via _proxy_for() helper commands: trigger REHASH reconnect on proxy config changes network: send CERT ADD before CAP END to beat K-line race Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -476,7 +476,9 @@ async def _cmd_rehash(router: Router) -> list[str]:
|
|||||||
new_net_cfg = new_cfg.networks[name]
|
new_net_cfg = new_cfg.networks[name]
|
||||||
if (old_net.cfg.host != new_net_cfg.host
|
if (old_net.cfg.host != new_net_cfg.host
|
||||||
or old_net.cfg.port != new_net_cfg.port
|
or old_net.cfg.port != new_net_cfg.port
|
||||||
or old_net.cfg.tls != new_net_cfg.tls):
|
or old_net.cfg.tls != new_net_cfg.tls
|
||||||
|
or old_net.cfg.proxy_host != new_net_cfg.proxy_host
|
||||||
|
or old_net.cfg.proxy_port != new_net_cfg.proxy_port):
|
||||||
await router.remove_network(name)
|
await router.remove_network(name)
|
||||||
await router.add_network(new_net_cfg)
|
await router.add_network(new_net_cfg)
|
||||||
lines.append(f" reconnected: {name}")
|
lines.append(f" reconnected: {name}")
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ class NetworkConfig:
|
|||||||
user: str = ""
|
user: str = ""
|
||||||
realname: str = ""
|
realname: str = ""
|
||||||
channels: list[str] = field(default_factory=list)
|
channels: list[str] = field(default_factory=list)
|
||||||
autojoin: bool = True
|
autojoin: bool = False
|
||||||
password: str | None = None
|
password: str | None = None
|
||||||
|
proxy_host: str | None = None
|
||||||
|
proxy_port: int | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@@ -100,6 +102,8 @@ def load(path: Path) -> Config:
|
|||||||
channels=net_raw.get("channels", []),
|
channels=net_raw.get("channels", []),
|
||||||
autojoin=net_raw.get("autojoin", True),
|
autojoin=net_raw.get("autojoin", True),
|
||||||
password=net_raw.get("password"),
|
password=net_raw.get("password"),
|
||||||
|
proxy_host=net_raw.get("proxy_host"),
|
||||||
|
proxy_port=net_raw.get("proxy_port"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not networks:
|
if not networks:
|
||||||
|
|||||||
@@ -827,11 +827,11 @@ class Network:
|
|||||||
log.info("[%s] SASL %s authentication successful", self.cfg.name, self._sasl_mechanism)
|
log.info("[%s] SASL %s authentication successful", self.cfg.name, self._sasl_mechanism)
|
||||||
self._status(f"SASL {self._sasl_mechanism} authenticated as {self._sasl_nick}")
|
self._status(f"SASL {self._sasl_mechanism} authenticated as {self._sasl_nick}")
|
||||||
self._sasl_complete.set()
|
self._sasl_complete.set()
|
||||||
await self.send_raw("CAP", "END")
|
# Register cert fingerprint BEFORE CAP END so NickServ processes
|
||||||
# If authenticated via PLAIN but a cert exists, register fingerprint
|
# it while we're still in capability negotiation (before K-line)
|
||||||
# immediately (before K-line can disconnect us)
|
|
||||||
if self._sasl_mechanism == "PLAIN" and self.data_dir:
|
if self._sasl_mechanism == "PLAIN" and self.data_dir:
|
||||||
await self._register_cert_fingerprint()
|
await self._register_cert_fingerprint()
|
||||||
|
await self.send_raw("CAP", "END")
|
||||||
return
|
return
|
||||||
|
|
||||||
if msg.command in ("902", "904", "905"):
|
if msg.command in ("902", "904", "905"):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from bouncer.backlog import Backlog
|
from bouncer.backlog import Backlog
|
||||||
from bouncer.config import Config, NetworkConfig
|
from bouncer.config import Config, NetworkConfig, ProxyConfig
|
||||||
from bouncer.irc import IRCMessage
|
from bouncer.irc import IRCMessage
|
||||||
from bouncer.namespace import decode_target, encode_message
|
from bouncer.namespace import decode_target, encode_message
|
||||||
from bouncer.network import Network
|
from bouncer.network import Network
|
||||||
@@ -91,12 +91,21 @@ class Router:
|
|||||||
self.clients: list[Client] = []
|
self.clients: list[Client] = []
|
||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
|
def _proxy_for(self, net_cfg: NetworkConfig) -> ProxyConfig:
|
||||||
|
"""Return the effective proxy config for a network."""
|
||||||
|
if net_cfg.proxy_host is not None:
|
||||||
|
return ProxyConfig(
|
||||||
|
host=net_cfg.proxy_host,
|
||||||
|
port=net_cfg.proxy_port or self.config.proxy.port,
|
||||||
|
)
|
||||||
|
return self.config.proxy
|
||||||
|
|
||||||
async def start_networks(self) -> None:
|
async def start_networks(self) -> None:
|
||||||
"""Connect to all configured networks."""
|
"""Connect to all configured networks."""
|
||||||
for name, net_cfg in self.config.networks.items():
|
for name, net_cfg in self.config.networks.items():
|
||||||
network = Network(
|
network = Network(
|
||||||
cfg=net_cfg,
|
cfg=net_cfg,
|
||||||
proxy_cfg=self.config.proxy,
|
proxy_cfg=self._proxy_for(net_cfg),
|
||||||
backlog=self.backlog,
|
backlog=self.backlog,
|
||||||
on_message=self._on_network_message,
|
on_message=self._on_network_message,
|
||||||
on_status=self._on_network_status,
|
on_status=self._on_network_status,
|
||||||
@@ -280,7 +289,7 @@ class Router:
|
|||||||
"""Create and start a new network at runtime."""
|
"""Create and start a new network at runtime."""
|
||||||
network = Network(
|
network = Network(
|
||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
proxy_cfg=self.config.proxy,
|
proxy_cfg=self._proxy_for(cfg),
|
||||||
backlog=self.backlog,
|
backlog=self.backlog,
|
||||||
on_message=self._on_network_message,
|
on_message=self._on_network_message,
|
||||||
on_status=self._on_network_status,
|
on_status=self._on_network_status,
|
||||||
|
|||||||
Reference in New Issue
Block a user