mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2025-10-07 01:33:15 +08:00
154 lines
3.0 KiB
Go
154 lines
3.0 KiB
Go
package core
|
|
|
|
/*
|
|
#cgo CFLAGS: -I./c/include
|
|
#include "lwip/udp.h"
|
|
*/
|
|
import "C"
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
type udpConnState uint
|
|
|
|
const (
|
|
udpNewConn udpConnState = iota
|
|
udpConnecting
|
|
udpConnected
|
|
udpClosed
|
|
)
|
|
|
|
type udpPacket struct {
|
|
data []byte
|
|
addr *net.UDPAddr
|
|
}
|
|
|
|
type udpConn struct {
|
|
sync.Mutex
|
|
|
|
pcb *C.struct_udp_pcb
|
|
handler UDPConnHandler
|
|
localAddr *net.UDPAddr
|
|
localIP C.ip_addr_t
|
|
localPort C.u16_t
|
|
state udpConnState
|
|
pending chan *udpPacket
|
|
}
|
|
|
|
func newUDPConn(pcb *C.struct_udp_pcb, handler UDPConnHandler, localIP C.ip_addr_t, localPort C.u16_t, localAddr, remoteAddr *net.UDPAddr) (UDPConn, error) {
|
|
conn := &udpConn{
|
|
handler: handler,
|
|
pcb: pcb,
|
|
localAddr: localAddr,
|
|
localIP: localIP,
|
|
localPort: localPort,
|
|
state: udpNewConn,
|
|
pending: make(chan *udpPacket, 1), // For DNS request payload.
|
|
}
|
|
|
|
conn.Lock()
|
|
conn.state = udpConnecting
|
|
conn.Unlock()
|
|
go func() {
|
|
err := handler.Connect(conn, remoteAddr)
|
|
if err != nil {
|
|
conn.Close()
|
|
} else {
|
|
conn.Lock()
|
|
conn.state = udpConnected
|
|
conn.Unlock()
|
|
// Once connected, send all pending data.
|
|
DrainPending:
|
|
for {
|
|
select {
|
|
case pkt := <-conn.pending:
|
|
err := conn.handler.ReceiveTo(conn, pkt.data, pkt.addr)
|
|
if err != nil {
|
|
break DrainPending
|
|
}
|
|
continue DrainPending
|
|
default:
|
|
break DrainPending
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (conn *udpConn) LocalAddr() *net.UDPAddr {
|
|
return conn.localAddr
|
|
}
|
|
|
|
func (conn *udpConn) checkState() error {
|
|
conn.Lock()
|
|
defer conn.Unlock()
|
|
|
|
switch conn.state {
|
|
case udpClosed:
|
|
return errors.New("connection closed")
|
|
case udpConnected:
|
|
return nil
|
|
case udpNewConn, udpConnecting:
|
|
return errors.New("not connected")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (conn *udpConn) isConnecting() bool {
|
|
conn.Lock()
|
|
defer conn.Unlock()
|
|
return conn.state == udpConnecting
|
|
}
|
|
|
|
func (conn *udpConn) ReceiveTo(data []byte, addr *net.UDPAddr) error {
|
|
if conn.isConnecting() {
|
|
pkt := &udpPacket{data: append([]byte(nil), data...), addr: addr}
|
|
select {
|
|
// Data will be dropped if pending is full.
|
|
case conn.pending <- pkt:
|
|
return nil
|
|
default:
|
|
}
|
|
}
|
|
if err := conn.checkState(); err != nil {
|
|
return err
|
|
}
|
|
err := conn.handler.ReceiveTo(conn, data, addr)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("write proxy failed: %v", err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (conn *udpConn) WriteFrom(data []byte, addr *net.UDPAddr) (int, error) {
|
|
if err := conn.checkState(); err != nil {
|
|
return 0, err
|
|
}
|
|
// FIXME any memory leaks?
|
|
cremoteIP := C.struct_ip_addr{}
|
|
if err := ipAddrATON(addr.IP.String(), &cremoteIP); err != nil {
|
|
return 0, err
|
|
}
|
|
buf := C.pbuf_alloc_reference(unsafe.Pointer(&data[0]), C.u16_t(len(data)), C.PBUF_ROM)
|
|
defer C.pbuf_free(buf)
|
|
C.udp_sendto(conn.pcb, buf, &conn.localIP, conn.localPort, &cremoteIP, C.u16_t(addr.Port))
|
|
return len(data), nil
|
|
}
|
|
|
|
func (conn *udpConn) Close() error {
|
|
connId := udpConnId{
|
|
src: conn.LocalAddr().String(),
|
|
}
|
|
conn.Lock()
|
|
conn.state = udpClosed
|
|
conn.Unlock()
|
|
udpConns.Delete(connId)
|
|
return nil
|
|
}
|