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:
1 parent
08fc2d79a8
commit
bcfe909aaa
15 files changed
+540
-97
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
@@ -349,9 +350,76 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
"UfwRunnable": ufwRunnable,
|
||||
"UfwEnabled": s.db.UfwEnabled(),
|
||||
"UfwRules": ufwRules,
|
||||
"UfwError": s.ufw.LastError(),
|
||||
"TunnelPort": s.TunnelPort(),
|
||||
"TunnelDefault": s.tunnelDefault,
|
||||
"TunnelHost": s.db.TunnelHost(),
|
||||
"TunnelCustom": s.db.TunnelPort() > 0,
|
||||
"Error": map[string]string{
|
||||
"tunnel_host": "Некорректный хост туннеля: нужен IP или имя без схемы и порта.",
|
||||
"tunnel_port": "Некорректный порт туннеля.",
|
||||
"tunnel_listen": "Не удалось занять новый порт туннеля на хосте (возможно, уже используется). Порт не изменён.",
|
||||
}[r.URL.Query().Get("err")],
|
||||
})
|
||||
}
|
||||
|
||||
// POST /settings/tunnel — host and port agents use for the direct tunnel.
|
||||
func (s *Server) handleSetTunnel(w http.ResponseWriter, r *http.Request) {
|
||||
if !checkCSRF(r) {
|
||||
http.Error(w, "bad csrf", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if s.tunnelDefault == 0 {
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
host := strings.TrimSpace(r.FormValue("tunnel_host"))
|
||||
host = strings.TrimSuffix(strings.TrimPrefix(host, "["), "]")
|
||||
if !validTunnelHost(host) {
|
||||
http.Redirect(w, r, "/settings?err=tunnel_host", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
// An empty port goes back to the one the server was started with.
|
||||
port, custom := s.tunnelDefault, ""
|
||||
if v := strings.TrimSpace(r.FormValue("tunnel_port")); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 1 || n > 65535 {
|
||||
http.Redirect(w, r, "/settings?err=tunnel_port", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
port = n
|
||||
if n != s.tunnelDefault {
|
||||
custom = v
|
||||
}
|
||||
}
|
||||
if err := s.setTunnelPort(port); err != nil {
|
||||
log.Printf("tunnel: move to port %d: %v", port, err)
|
||||
http.Redirect(w, r, "/settings?err=tunnel_listen", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
_ = s.db.SetConfig(cfgTunnelPort, custom)
|
||||
_ = s.db.SetConfig(cfgTunnelHost, host)
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// validTunnelHost accepts an empty value, an IP, or a bare host name.
|
||||
func validTunnelHost(host string) bool {
|
||||
if host == "" || net.ParseIP(host) != nil {
|
||||
return true
|
||||
}
|
||||
if len(host) > 253 {
|
||||
return false
|
||||
}
|
||||
for _, c := range host {
|
||||
switch {
|
||||
case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '-', c == '.', c == '_':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Server) handleSetHost(w http.ResponseWriter, r *http.Request) {
|
||||
if !checkCSRF(r) {
|
||||
http.Error(w, "bad csrf", http.StatusForbidden)
|
||||
@@ -408,7 +476,12 @@ func (s *Server) handleUfwToggle(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "bad csrf", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
_ = s.db.SetUfwEnabled(r.FormValue("enabled") == "1")
|
||||
on := r.FormValue("enabled") == "1"
|
||||
_ = s.db.SetUfwEnabled(on)
|
||||
if on {
|
||||
// Open what forwards created while the integration was off need.
|
||||
s.ufw.sync()
|
||||
}
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user