mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-09-27 13:12:14 +08:00
199 lines
4.6 KiB
Go
199 lines
4.6 KiB
Go
package proxy
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/pion/ice/v2"
|
|
"go.uber.org/zap"
|
|
|
|
epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc"
|
|
)
|
|
|
|
const (
|
|
StunMagicCookie uint32 = 0x2112A442
|
|
|
|
maxSegmentSize = (1 << 16) - 1
|
|
)
|
|
|
|
type KernelProxy struct {
|
|
listenPort int
|
|
|
|
nat *NAT
|
|
natRule *NATRule
|
|
|
|
ep *net.UDPAddr
|
|
cp *ice.CandidatePair
|
|
connICE *ice.Conn
|
|
connUser *net.UDPConn
|
|
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewKernelProxy(nat *NAT, listenPort int) (*KernelProxy, error) {
|
|
p := &KernelProxy{
|
|
nat: nat,
|
|
listenPort: listenPort,
|
|
logger: zap.L().Named("proxy").With(zap.String("type", "kernel")),
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (p *KernelProxy) Close() error {
|
|
if p.connUser != nil {
|
|
p.connUser.SetWriteDeadline(time.Now().Add(1 * time.Second)) // TODO: really required?
|
|
if err := p.connUser.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *KernelProxy) UpdateListenPort(listenPort int) error {
|
|
return p.Update(nil, nil, listenPort)
|
|
}
|
|
|
|
func (p *KernelProxy) UpdateCandidatePair(cp *ice.CandidatePair, conn *ice.Conn) (*net.UDPAddr, error) {
|
|
if err := p.Update(cp, conn, -1); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return p.ep, nil
|
|
}
|
|
|
|
func (p *KernelProxy) Update(newCP *ice.CandidatePair, newConnICE *ice.Conn, newListenPort int) error {
|
|
var err error
|
|
|
|
if newListenPort > 0 {
|
|
p.listenPort = newListenPort
|
|
}
|
|
|
|
if newCP != nil {
|
|
p.cp = newCP
|
|
}
|
|
|
|
if p.cp != nil {
|
|
switch p.Type() {
|
|
case epdiscproto.ProxyType_KERNEL_NAT:
|
|
p.ep = &net.UDPAddr{
|
|
IP: net.ParseIP(p.cp.Remote.Address()),
|
|
Port: p.cp.Remote.Port(),
|
|
}
|
|
|
|
// Delete any old SPAT rule
|
|
if p.natRule != nil {
|
|
if err := p.natRule.Delete(); err != nil {
|
|
return fmt.Errorf("failed to delete rule: %w", err)
|
|
}
|
|
}
|
|
|
|
// Setup SNAT redirect (WireGuard listen-port -> STUN port)
|
|
if p.natRule, err = p.nat.MasqueradeSourcePort(p.listenPort, p.cp.Local.Port(), p.ep); err != nil {
|
|
return err
|
|
}
|
|
|
|
case epdiscproto.ProxyType_KERNEL_CONN:
|
|
// We cant to anything for prfx and relay candidates.
|
|
// Let them pass through the userspace connection
|
|
|
|
var create = false
|
|
if p.connUser == nil {
|
|
// We lazily create the user connection on demand to avoid opening unused sockets
|
|
create = true
|
|
} else if ra, ok := p.connUser.RemoteAddr().(*net.UDPAddr); ok && ra.Port != p.listenPort {
|
|
// Also recreate the user connection in case the WireGuard listen port has changed
|
|
create = true
|
|
}
|
|
|
|
var newConnUser *net.UDPConn
|
|
if create {
|
|
if newConnUser, err = p.newUserConn(newConnICE); err != nil {
|
|
return fmt.Errorf("failed to setup user connection: %w", err)
|
|
}
|
|
p.logger.Info("Setup user-space proxy connection",
|
|
zap.Any("localAddress", newConnUser.LocalAddr()),
|
|
zap.Any("remoteAddress", newConnUser.RemoteAddr()))
|
|
}
|
|
|
|
// Start copying if the underlying ice.Conn has changed
|
|
if newConnICE != p.connICE || newConnUser != p.connUser {
|
|
if p.connUser != nil {
|
|
if err := p.connUser.Close(); err != nil {
|
|
return fmt.Errorf("failed to close old user connection: %w", err)
|
|
}
|
|
}
|
|
|
|
p.connICE = newConnICE
|
|
p.connUser = newConnUser
|
|
|
|
// Bi-directional copy between ICE and loopback UDP sockets
|
|
go p.copy(newConnICE, p.connUser)
|
|
go p.copy(p.connUser, newConnICE)
|
|
}
|
|
|
|
p.ep = p.connUser.LocalAddr().(*net.UDPAddr)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *KernelProxy) copy(dst io.Writer, src io.Reader) {
|
|
buf := make([]byte, maxSegmentSize)
|
|
for {
|
|
// TODO: Check why this is not working
|
|
// if _, err := io.Copy(dst, src); err != nil {
|
|
// p.logger.Error("Failed copy", zap.Error(err))
|
|
// }
|
|
|
|
n, err := src.Read(buf)
|
|
if err != nil {
|
|
if errors.Is(err, ice.ErrClosed) || errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) {
|
|
return
|
|
}
|
|
|
|
p.logger.Error("Failed to read", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
if _, err = dst.Write(buf[:n]); err != nil {
|
|
p.logger.Error("Failed to write", zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *KernelProxy) newUserConn(iceConn *ice.Conn) (*net.UDPConn, error) {
|
|
// User-space proxying
|
|
rAddr := net.UDPAddr{
|
|
IP: net.IPv6loopback,
|
|
Port: int(p.listenPort),
|
|
}
|
|
|
|
lAddr := net.UDPAddr{
|
|
IP: net.IPv6loopback,
|
|
Port: 0, // choose randomly
|
|
}
|
|
|
|
conn, err := net.DialUDP("udp", &lAddr, &rAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (p *KernelProxy) Type() epdiscproto.ProxyType {
|
|
if p.cp == nil {
|
|
return epdiscproto.ProxyType_NO_PROXY
|
|
} else if p.cp.Local.Type() == ice.CandidateTypeHost || p.cp.Local.Type() == ice.CandidateTypeServerReflexive {
|
|
return epdiscproto.ProxyType_KERNEL_NAT
|
|
} else {
|
|
return epdiscproto.ProxyType_KERNEL_CONN
|
|
}
|
|
}
|