feat: tunnel host and port in the panel settings, ufw kept in sync

The direct tunnel port (8446 by default) and the host agents dial for
it can be set in Settings. The server moves its listeners at once and
agents pick the address up from the panel: on connect and with every
poll, unless TUNNEL_ADDR / TUNNEL_PORT pin it locally.

ufw: run with a C locale (the output is translated), do not adopt rules
that existed before the panel, skip loopback-only listeners, re-open
what saved forwards need on start and when the integration is turned
on, move the shared tunnel rule with the port and drop it when no
direct forward is left. The last ufw error is shown in Settings.

The forwards page shows the real tunnel and panel ports.
This commit is contained in:
redtea committed 2026-10-13 14:50:50 +03:00
1 parent 08fc2d79a8
commit bcfe909aaa
15 files changed
+540 -97

No files matched your search

+17 -13
View File
@@ -15,12 +15,12 @@ import (
// --- direct TCP mux tunnel ---
// runTunnel keeps a persistent connection to the host's 8446 tunnel, dialing
// client-side ports on demand for "tcp" forward rules.
func (a *Agent) runTunnel(ctx context.Context) {
// runTunnel keeps a persistent connection to the host's tunnel at addr, dialing
// client-side ports on demand for "tcp" forward rules, until ctx is cancelled.
func (a *Agent) runTunnel(ctx context.Context, addr string) {
backoff := time.Second
for ctx.Err() == nil {
if err := a.tunnelOnce(ctx); err != nil {
if err := a.tunnelOnce(ctx, addr); err != nil {
select {
case <-time.After(backoff):
case <-ctx.Done():
@@ -35,13 +35,15 @@ func (a *Agent) runTunnel(ctx context.Context) {
}
}
func (a *Agent) tunnelOnce(ctx context.Context) error {
func (a *Agent) tunnelOnce(ctx context.Context, addr string) error {
d := net.Dialer{Timeout: 10 * time.Second}
conn, err := d.DialContext(ctx, "tcp", a.cfg.TunnelAddr)
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return err
}
defer conn.Close()
// Unblock the read loop when the tunnel is stopped or moved.
defer context.AfterFunc(ctx, func() { conn.Close() })()
if tc, ok := conn.(*net.TCPConn); ok {
_ = tc.SetNoDelay(true)
}
@@ -65,7 +67,7 @@ func (a *Agent) tunnelOnce(ctx context.Context) error {
return err
}
_ = conn.SetDeadline(time.Time{})
log.Printf("tunnel: connected to %s", a.cfg.TunnelAddr)
log.Printf("tunnel: connected to %s", addr)
tc := &tunClient{conn: conn, streams: map[uint32]*agentStream{}}
return tc.readLoop()
@@ -194,12 +196,12 @@ func (t *tunClient) shutdown() {
// --- direct UDP forwarding ---
// runUDP maintains the agent's UDP endpoint on the host's 8446 channel and
// runUDP maintains the agent's UDP endpoint on the host's tunnel at addr and
// relays datagrams to/from client-side UDP ports for "udp" forward rules.
func (a *Agent) runUDP(ctx context.Context) {
func (a *Agent) runUDP(ctx context.Context, addr string) {
backoff := time.Second
for ctx.Err() == nil {
if err := a.udpOnce(ctx); err != nil {
if err := a.udpOnce(ctx, addr); err != nil {
select {
case <-time.After(backoff):
case <-ctx.Done():
@@ -214,8 +216,8 @@ func (a *Agent) runUDP(ctx context.Context) {
}
}
func (a *Agent) udpOnce(ctx context.Context) error {
raddr, err := net.ResolveUDPAddr("udp", a.cfg.TunnelAddr)
func (a *Agent) udpOnce(ctx context.Context, addr string) error {
raddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return err
}
@@ -224,6 +226,8 @@ func (a *Agent) udpOnce(ctx context.Context) error {
return err
}
defer conn.Close()
// Unblock the read loop when the tunnel is stopped or moved.
defer context.AfterFunc(ctx, func() { conn.Close() })()
key := tunnel.AuthKey(a.state.Token)
register := func() {
@@ -268,7 +272,7 @@ func (a *Agent) udpOnce(ctx context.Context) error {
}
type udpClient struct {
server *net.UDPConn // connection back to host:8446
server *net.UDPConn // connection back to the host tunnel
mu sync.Mutex
dials map[uint32]*net.UDPConn // sid -> client-side socket
}