mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-10-05 16:57:01 +08:00
daemon: use per-interface features
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
198
pkg/daemon/feature/epdisc/proxy/proxy_kernel.go
Normal file
198
pkg/daemon/feature/epdisc/proxy/proxy_kernel.go
Normal file
@@ -0,0 +1,198 @@
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user