mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
Merge pull request #36 from smediainfo/feat/host-socket-protect
feat(mobile): add host socket protector (SetProtect) for tun/VPN hosts
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/logx"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netctl"
|
||||
|
||||
// встроенные Mozilla CA roots для CGO_ENABLED=0 сборок (Android).
|
||||
_ "golang.org/x/crypto/x509roots/fallback"
|
||||
@@ -98,7 +99,7 @@ func newBootstrapTransport(endpoints []DohEndpoint) *http.Transport {
|
||||
for _, ep := range endpoints {
|
||||
bootstrap[ep.Hostname] = ep.BootstrapIPs
|
||||
}
|
||||
dialer := &net.Dialer{Timeout: dohDialerTimeout, KeepAlive: dohDialerKeepAlive}
|
||||
dialer := &net.Dialer{Timeout: dohDialerTimeout, KeepAlive: dohDialerKeepAlive, Control: netctl.Apply}
|
||||
|
||||
return &http.Transport{
|
||||
MaxIdleConns: 8,
|
||||
@@ -440,6 +441,7 @@ func newAppDialer(dial dialFunc) net.Dialer {
|
||||
Timeout: appDialerTimeout,
|
||||
KeepAlive: appDialerKeepAlive,
|
||||
Resolver: &net.Resolver{PreferGo: true, Dial: dial},
|
||||
Control: netctl.Apply,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
-10
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/transport/v4"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netctl"
|
||||
)
|
||||
|
||||
// DirectNet реализует transport.Net через стандартный net.
|
||||
@@ -34,32 +35,84 @@ type directTCPListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
// All socket-creating methods route through a net.Dialer / net.ListenConfig
|
||||
// carrying netctl.Apply as Control so the host can protect each fd from the
|
||||
// VPN tunnel. (net.DialUDP / net.ListenUDP take no Control, so we go via
|
||||
// Dialer/ListenConfig and type-assert back to the concrete UDP/TCP conn.)
|
||||
func (DirectNet) ListenPacket(network string, address string) (net.PacketConn, error) {
|
||||
return net.ListenPacket(network, address) //nolint:noctx
|
||||
return (&net.ListenConfig{Control: netctl.Apply}).ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func (DirectNet) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) {
|
||||
return net.ListenUDP(network, locAddr)
|
||||
}
|
||||
|
||||
func (DirectNet) ListenTCP(network string, laddr *net.TCPAddr) (transport.TCPListener, error) {
|
||||
listener, err := net.ListenTCP(network, laddr)
|
||||
addr := ""
|
||||
if locAddr != nil {
|
||||
addr = locAddr.String()
|
||||
}
|
||||
pc, err := (&net.ListenConfig{Control: netctl.Apply}).ListenPacket(context.Background(), network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return directTCPListener{listener}, nil
|
||||
udp, ok := pc.(*net.UDPConn)
|
||||
if !ok {
|
||||
_ = pc.Close()
|
||||
return nil, fmt.Errorf("netconn: expected *net.UDPConn, got %T", pc)
|
||||
}
|
||||
return udp, nil
|
||||
}
|
||||
|
||||
func (DirectNet) ListenTCP(network string, laddr *net.TCPAddr) (transport.TCPListener, error) {
|
||||
addr := ""
|
||||
if laddr != nil {
|
||||
addr = laddr.String()
|
||||
}
|
||||
l, err := (&net.ListenConfig{Control: netctl.Apply}).Listen(context.Background(), network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tl, ok := l.(*net.TCPListener)
|
||||
if !ok {
|
||||
_ = l.Close()
|
||||
return nil, fmt.Errorf("netconn: expected *net.TCPListener, got %T", l)
|
||||
}
|
||||
return directTCPListener{tl}, nil
|
||||
}
|
||||
|
||||
func (DirectNet) Dial(network, address string) (net.Conn, error) {
|
||||
return net.Dial(network, address) //nolint:noctx
|
||||
return (&net.Dialer{Control: netctl.Apply}).Dial(network, address)
|
||||
}
|
||||
|
||||
func (DirectNet) DialUDP(network string, laddr, raddr *net.UDPAddr) (transport.UDPConn, error) {
|
||||
return net.DialUDP(network, laddr, raddr)
|
||||
d := &net.Dialer{Control: netctl.Apply}
|
||||
if laddr != nil {
|
||||
d.LocalAddr = laddr
|
||||
}
|
||||
c, err := d.Dial(network, raddr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
udp, ok := c.(*net.UDPConn)
|
||||
if !ok {
|
||||
_ = c.Close()
|
||||
return nil, fmt.Errorf("netconn: expected *net.UDPConn, got %T", c)
|
||||
}
|
||||
return udp, nil
|
||||
}
|
||||
|
||||
func (DirectNet) DialTCP(network string, laddr, raddr *net.TCPAddr) (transport.TCPConn, error) {
|
||||
return net.DialTCP(network, laddr, raddr)
|
||||
d := &net.Dialer{Control: netctl.Apply}
|
||||
if laddr != nil {
|
||||
d.LocalAddr = laddr
|
||||
}
|
||||
c, err := d.Dial(network, raddr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcp, ok := c.(*net.TCPConn)
|
||||
if !ok {
|
||||
_ = c.Close()
|
||||
return nil, fmt.Errorf("netconn: expected *net.TCPConn, got %T", c)
|
||||
}
|
||||
return tcp, nil
|
||||
}
|
||||
|
||||
func (DirectNet) ResolveIPAddr(network, address string) (*net.IPAddr, error) {
|
||||
@@ -87,10 +140,16 @@ func (DirectNet) InterfaceByName(name string) (*transport.Interface, error) {
|
||||
}
|
||||
|
||||
func (DirectNet) CreateDialer(dialer *net.Dialer) transport.Dialer {
|
||||
if dialer.Control == nil {
|
||||
dialer.Control = netctl.Apply
|
||||
}
|
||||
return directDialer{Dialer: dialer}
|
||||
}
|
||||
|
||||
func (DirectNet) CreateListenConfig(listenerConfig *net.ListenConfig) transport.ListenConfig {
|
||||
if listenerConfig.Control == nil {
|
||||
listenerConfig.Control = netctl.Apply
|
||||
}
|
||||
return directListenConfig{ListenConfig: listenerConfig}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Package netctl carries a process-global socket Control hook applied to every
|
||||
// OUTBOUND socket the vendored free-turn client opens. The host installs it so
|
||||
// those sockets are excluded from the VPN tunnel (Android VpnService.protect on
|
||||
// the raw fd) — without it the client's own TURN/VK/DNS traffic loops back into
|
||||
// the tunnel and hangs. This patch is local to our vendored copy: upstream has
|
||||
// no such hook, and the dialers it creates (netconn.DirectNet, dnsdial,
|
||||
// turndial) are routed through Apply below.
|
||||
package netctl
|
||||
|
||||
import "syscall"
|
||||
|
||||
var control func(network, address string, c syscall.RawConn) error
|
||||
|
||||
// SetControl installs the per-socket protector. Call before the client starts.
|
||||
// nil (the default) makes Apply a no-op, so desktop builds are unaffected.
|
||||
func SetControl(fn func(network, address string, c syscall.RawConn) error) {
|
||||
control = fn
|
||||
}
|
||||
|
||||
// Apply is assigned as the Control func of every outbound net.Dialer /
|
||||
// net.ListenConfig in the vendored client. It reads `control` at dial time so
|
||||
// the host may register the protector before or after the client starts.
|
||||
func Apply(network, address string, c syscall.RawConn) error {
|
||||
if control != nil {
|
||||
return control(network, address, c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/turn/v5"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netconn"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netctl"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/randx"
|
||||
)
|
||||
|
||||
@@ -83,10 +84,15 @@ func Open(ctx context.Context, cfg Config, peer *net.UDPAddr, user, pass, rawAdd
|
||||
closeConn func() error
|
||||
)
|
||||
if cfg.TransportUDP {
|
||||
c, derr := net.DialUDP("udp", nil, turnServerUDPAddr) //nolint:noctx
|
||||
raw, derr := (&net.Dialer{Control: netctl.Apply}).Dial("udp", turnServerUDPAddr.String())
|
||||
if derr != nil {
|
||||
return nil, fmt.Errorf("dial TURN (udp): %w", derr)
|
||||
}
|
||||
c, ok := raw.(*net.UDPConn)
|
||||
if !ok {
|
||||
_ = raw.Close()
|
||||
return nil, fmt.Errorf("turndial: expected *net.UDPConn, got %T", raw)
|
||||
}
|
||||
turnConn = &netconn.ConnectedUDPConn{UDPConn: c}
|
||||
closeConn = c.Close
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package mobile
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netctl"
|
||||
)
|
||||
|
||||
// Protector is implemented by the host to keep the client's own sockets out of
|
||||
// a VPN tunnel it is feeding. On Android that is VpnService.protect(fd); on
|
||||
// other tun-based hosts the equivalent. Without it the client's TURN / VK API /
|
||||
// DNS traffic is routed back into the tunnel and the connection deadlocks.
|
||||
type Protector interface {
|
||||
// Protect is invoked once for every outbound socket the client opens, with
|
||||
// the socket's raw file descriptor, before it connects.
|
||||
Protect(fd int) bool
|
||||
}
|
||||
|
||||
// SetProtect installs the host socket protector. Pass nil to clear it (the
|
||||
// default is a no-op, so desktop builds are unaffected). May be called before
|
||||
// or after Start; it is read at dial time.
|
||||
func SetProtect(p Protector) {
|
||||
if p == nil {
|
||||
netctl.SetControl(nil)
|
||||
return
|
||||
}
|
||||
netctl.SetControl(func(_, _ string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) { p.Protect(int(fd)) })
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user