Files
cunicu/pkg/daemon/feature/epdisc/proxy/proxy_kernel.go
Steffen Vogel 92a7ad2f7f daemon: use per-interface features
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2022-10-07 18:30:50 +02:00

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
}
}