mirror of
https://github.com/safing/portmaster.git
synced 2026-06-19 20:44:35 +00:00
Add a synchronous HookMgr[T] that lets callers register pre-connect hooks before SPN dials a home hub. The IVPN interop layer subscribes to this hook and uses Linux ip-rule/ip-route to steer SPN hub IPs through a dedicated routing table (717) pointing to the non-VPN default gateway, preventing SPN control traffic from being tunnelled into IVPN. - service/mgr: add generic HookMgr[T] (synchronous, cancellable) - spn/captain: expose HookSPNConnecting; invoke it in connectToHomeHub - service/netenv: add GatewayInfo + GatewaysInfo() with interface/mask - service/interop/ivpn: add ensureSpnHubBypassVpnRoutes managing policy routing; call it from the SPN pre-connect hook and on VPN stop/connect - nfq/packet: add hex comments next to mark constants https://github.com/safing/portmaster-shadow/issues/34
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
package interop
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"sync/atomic"
|
|
|
|
"github.com/safing/portmaster/base/config"
|
|
"github.com/safing/portmaster/service/firewall"
|
|
"github.com/safing/portmaster/service/firewall/interception"
|
|
"github.com/safing/portmaster/service/interop/ivpn"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
"github.com/safing/portmaster/service/network"
|
|
"github.com/safing/portmaster/spn/hub"
|
|
)
|
|
|
|
// Interface for separate interoperability objects.
|
|
// Each object can implement interoperability with a specific third-party application,
|
|
// such as IVPN client, to exchange information and coordinate actions for better compatibility and user experience.
|
|
type interopModule interface {
|
|
Start() error
|
|
PingHandler() error
|
|
VerdictHandler(conn *network.Connection) (network.Verdict, string, bool)
|
|
}
|
|
|
|
type instance interface {
|
|
Config() *config.Config
|
|
Interception() *interception.Interception
|
|
GetHookSPNConnecting() *mgr.HookMgr[hub.Announcement]
|
|
}
|
|
|
|
// Module for interoperability with third-party applications
|
|
type Interoperability struct {
|
|
mgr *mgr.Manager
|
|
instance instance
|
|
cfgDnsNameServers config.StringArrayOption
|
|
dnsListenAddress string
|
|
interopModules []interopModule
|
|
verdictHandlerRegistered atomic.Bool
|
|
}
|
|
|
|
// Manager returns the module manager.
|
|
func (u *Interoperability) Manager() *mgr.Manager {
|
|
return u.mgr
|
|
}
|
|
|
|
// Start starts the module.
|
|
func (u *Interoperability) Start() error {
|
|
return start()
|
|
}
|
|
|
|
// Stop stops the module.
|
|
func (u *Interoperability) Stop() error {
|
|
return stop()
|
|
}
|
|
|
|
func (u *Interoperability) DnsListenAddress() string {
|
|
return u.dnsListenAddress
|
|
}
|
|
func (u *Interoperability) DnsNameServers() []string {
|
|
return u.cfgDnsNameServers()
|
|
}
|
|
func (u *Interoperability) EvtConfigChange() <-chan struct{} {
|
|
return u.instance.Config().EventConfigChange.Subscribe("interoperability: config change detection", 10).Events()
|
|
}
|
|
func (u *Interoperability) Interception() *interception.Interception {
|
|
return u.instance.Interception()
|
|
}
|
|
func (u *Interoperability) GetHookSPNConnecting() *mgr.HookMgr[hub.Announcement] {
|
|
return u.instance.GetHookSPNConnecting()
|
|
}
|
|
|
|
func start() error {
|
|
for _, im := range module.interopModules {
|
|
if err := im.Start(); err != nil {
|
|
return errors.New("failed to start interoperability module: " + err.Error())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stop() error {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
module *Interoperability
|
|
shimLoaded atomic.Bool
|
|
)
|
|
|
|
func New(instance instance) (*Interoperability, error) {
|
|
if !shimLoaded.CompareAndSwap(false, true) {
|
|
return nil, errors.New("only one instance allowed")
|
|
}
|
|
|
|
// Determine Portmaster's local DNS resolver listen address for use in IVPN client configuration.
|
|
// Ot is ok to check it once at startup, since it can only be changed by restarting Portmaster with a different config.
|
|
var err error
|
|
dnsListenAddress := config.GetAsString("dns/listenAddress", "")()
|
|
dnsListenAddress, _, err = net.SplitHostPort(dnsListenAddress)
|
|
if err != nil || net.ParseIP(dnsListenAddress) == nil || dnsListenAddress == "" || dnsListenAddress == "0.0.0.0" {
|
|
dnsListenAddress = "127.0.0.1"
|
|
}
|
|
|
|
// Create module instance.
|
|
m := mgr.New("Interop")
|
|
module = &Interoperability{
|
|
mgr: m,
|
|
instance: instance,
|
|
cfgDnsNameServers: config.GetAsStringArray("dns/nameservers", []string{}),
|
|
dnsListenAddress: dnsListenAddress,
|
|
interopModules: make([]interopModule, 0, 1),
|
|
}
|
|
|
|
if err := module.prep(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return module, nil
|
|
}
|
|
|
|
func (i *Interoperability) prep() error {
|
|
i.interopModules = append(i.interopModules, ivpn.NewInteropIvpn(i))
|
|
return i.registerAPIEndpoints()
|
|
}
|
|
|
|
// EnsureVerdictHandlerRegistered registers the interoperability module's verdict handler
|
|
// with the firewall module if not already registered.
|
|
// We do this lazily here only when we need it.
|
|
func (i *Interoperability) EnsureVerdictHandlerRegistered() {
|
|
if i.verdictHandlerRegistered.CompareAndSwap(false, true) {
|
|
firewall.SetExternalVerdictHandler(i.verdict_handler)
|
|
}
|
|
}
|