/* SPDX-License-Identifier: MIT * * Phobos */ package tun2socks import ( "context" "net" "net/netip" "sync" "time" ) const ( udpIdleTimeout = 60 * time.Second udpDatagramSize = 2000 ) type udpRelay struct { session PacketSession logf func(string, ...any) mu sync.Mutex flows map[netip.AddrPort]net.Conn closed bool release func() } type udpMultiplexer struct { dialer Dialer logf func(string, ...any) mu sync.Mutex relays map[netip.AddrPort]*udpRelay closed bool } func newUDPMultiplexer(dialer Dialer, logf func(string, ...any)) *udpMultiplexer { return &udpMultiplexer{dialer: dialer, logf: logf, relays: make(map[netip.AddrPort]*udpRelay)} } func (m *udpMultiplexer) attach(source, target netip.AddrPort, conn net.Conn) { relay, err := m.relayFor(source) if err != nil { m.logf("tun2socks: cannot open a UDP session for %v: %v", source, err) conn.Close() return } if !relay.register(target, conn) { conn.Close() return } go relay.pumpFlow(target, conn) } func (m *udpMultiplexer) relayFor(source netip.AddrPort) (*udpRelay, error) { m.mu.Lock() if m.closed { m.mu.Unlock() return nil, net.ErrClosed } if relay, ok := m.relays[source]; ok { m.mu.Unlock() return relay, nil } m.mu.Unlock() session, err := m.dialer.DialUDP(context.Background()) if err != nil { return nil, err } m.mu.Lock() if existing, ok := m.relays[source]; ok { m.mu.Unlock() session.Close() return existing, nil } relay := &udpRelay{ session: session, logf: m.logf, flows: make(map[netip.AddrPort]net.Conn), release: func() { m.forget(source) }, } if m.closed { m.mu.Unlock() session.Close() return nil, net.ErrClosed } m.relays[source] = relay m.mu.Unlock() go relay.pumpSession() return relay, nil } func (m *udpMultiplexer) forget(source netip.AddrPort) { m.mu.Lock() delete(m.relays, source) m.mu.Unlock() } func (m *udpMultiplexer) close() { m.mu.Lock() m.closed = true relays := make([]*udpRelay, 0, len(m.relays)) for _, relay := range m.relays { relays = append(relays, relay) } m.relays = make(map[netip.AddrPort]*udpRelay) m.mu.Unlock() for _, relay := range relays { relay.close() } } func (r *udpRelay) register(target netip.AddrPort, conn net.Conn) bool { r.mu.Lock() defer r.mu.Unlock() if r.closed { return false } if previous, ok := r.flows[target]; ok { previous.Close() } r.flows[target] = conn return true } func (r *udpRelay) unregister(target netip.AddrPort) { r.mu.Lock() delete(r.flows, target) empty := len(r.flows) == 0 r.mu.Unlock() if empty { r.close() } } func (r *udpRelay) close() { r.mu.Lock() if r.closed { r.mu.Unlock() return } r.closed = true flows := make([]net.Conn, 0, len(r.flows)) for _, conn := range r.flows { flows = append(flows, conn) } r.flows = nil r.mu.Unlock() r.session.Close() for _, conn := range flows { conn.Close() } if r.release != nil { r.release() } } func (r *udpRelay) pumpFlow(target netip.AddrPort, conn net.Conn) { defer r.unregister(target) defer conn.Close() buf := make([]byte, udpDatagramSize) for { conn.SetReadDeadline(time.Now().Add(udpIdleTimeout)) n, err := conn.Read(buf) if err != nil { return } if err := r.session.WriteTo(buf[:n], target); err != nil { return } } } func (r *udpRelay) pumpSession() { buf := make([]byte, udpDatagramSize) for { n, source, err := r.session.ReadFrom(buf) if err != nil { r.close() return } r.mu.Lock() conn := r.flows[source] r.mu.Unlock() if conn == nil { continue } if _, err := conn.Write(buf[:n]); err != nil { conn.Close() } } }