diff --git a/README.md b/README.md index 592f995..52e0613 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,6 @@ rules: - [x] 支持 LuCI - [x] 优化部署流程 - [ ] 支持 SOCK5 Auth -- [ ] 支持 UDP +- [x] 支持 UDP - [ ] 支持 IPv6 - [ ] 性能提升 \ No newline at end of file diff --git a/build.sh b/build.sh index 2ff46b2..7d52983 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/bin/sh project_name="ua3f" -release_version="0.2.3" +release_version="0.3.0" target=cmd/ua3f.go release_dir=./bin diff --git a/cmd/ua3f.go b/cmd/ua3f.go index be02e37..38ab76f 100644 --- a/cmd/ua3f.go +++ b/cmd/ua3f.go @@ -17,7 +17,7 @@ import ( "github.com/sirupsen/logrus" ) -var version = "0.2.3" +var version = "0.3.0" var payloadByte []byte var cache *expirable.LRU[string, string] var HTTP_METHOD = []string{"GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS", "TRACE", "CONNECT"} @@ -91,6 +91,12 @@ func process(client net.Conn) { } target, destAddrPort, err := Socks5Connect(client) if err != nil { + // UDP + if strings.Contains(err.Error(), "UDP Associate") { + Socks5UDP(client) + client.Close() + return + } logrus.Error("Connect failed: ", err) client.Close() return @@ -128,48 +134,134 @@ func Socks5Auth(client net.Conn) (err error) { return nil } -// func Socks5UDP() { -// https://datatracker.ietf.org/doc/html/rfc1928 -// // _, _ = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x7f, 0, 0, 0x1, 0x04, 0x38}) -// _, _ = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0x04, 0x38}) -// server, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 1080}) -// _, _ = server.Read(buf[:4]) -// frag, atyp := buf[2], buf[3] -// addr := "" -// switch atyp { -// case 1: -// n, err = server.Read(buf[:4]) -// if n != 4 { -// return nil, "", errors.New("invalid IPv4:" + err.Error()) -// } -// addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]) -// case 3: -// n, err = server.Read(buf[:1]) -// if n != 1 { -// return nil, "", errors.New("invalid hostname:" + err.Error()) -// } -// addrLen := int(buf[0]) -// n, err = server.Read(buf[:addrLen]) -// if n != addrLen { -// return nil, "", errors.New("invalid hostname:" + err.Error()) -// } -// addr = string(buf[:addrLen]) -// case 4: -// return nil, "", errors.New("IPv6: no supported yet") -// default: -// return nil, "", errors.New("invalid atyp") -// } -// n, err = server.Read(buf[:2]) -// port := binary.BigEndian.Uint16(buf[:2]) -// destAddrPort := fmt.Sprintf("%s:%d", addr, port) -// logrus.Debug(fmt.Sprintf("Connecting %s", destAddrPort)) -// dest, err := net.Dial("udp", destAddrPort) -// if err != nil { -// return nil, destAddrPort, errors.New("dial dst:" + err.Error()) -// } -// logrus.Debug(fmt.Sprintf("Connected %s", destAddrPort)) -// -// } +func isAlive(conn net.Conn) bool { + one := make([]byte, 1) + conn.SetReadDeadline(time.Now().Add(time.Second * 5)) + _, err := conn.Read(one) + if err != nil { + if err == io.EOF { + logrus.Debug(fmt.Sprintf("[%s] isAlive: EOF", conn.RemoteAddr().String())) + return false + } else if strings.Contains(err.Error(), "use of closed network connection") { + logrus.Debug(fmt.Sprintf("[%s] isAlive: closed", conn.RemoteAddr().String())) + return false + } else if strings.Contains(err.Error(), "i/o timeout") { + logrus.Debug(fmt.Sprintf("[%s] isAlive: timeout", conn.RemoteAddr().String())) + return true + } else { + logrus.Debug(fmt.Sprintf("[%s] isAlive: %s", conn.RemoteAddr().String(), err.Error())) + return false + } + } + conn.SetReadDeadline(time.Time{}) + return true +} + +func Socks5UDP(client net.Conn) { + udpserver, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + if err != nil { + logrus.Error(fmt.Sprintf("[%s][UDP] ListenUDP failed: %s", client.RemoteAddr().String(), err.Error())) + return + } + _, port, _ := net.SplitHostPort(udpserver.LocalAddr().String()) + logrus.Debug(fmt.Sprintf("[%s][UDP] ListenUDP on %s", client.RemoteAddr().String(), port)) + portInt, _ := net.LookupPort("udp", port) + portBytes := make([]byte, 2) + binary.BigEndian.PutUint16(portBytes, uint16(portInt)) + _, err = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, portBytes[0], portBytes[1]}) + if err != nil { + logrus.Error(fmt.Sprintf("[%s][UDP] Write rsp failed: %s", client.RemoteAddr().String(), err.Error())) + return + } + buf := make([]byte, 65535) + udpPortMap := make(map[string][]byte) + var clientAddr *net.UDPAddr + var isDomain bool = false + for { + udpserver.SetReadDeadline(time.Now().Add(time.Second * 10)) + n, fromAddr, err := udpserver.ReadFromUDP(buf) + if err != nil { + if strings.Contains(err.Error(), "i/o timeout") { + logrus.Debug(fmt.Sprintf("[%s][UDP] ReadFromUDP failed: %s", client.RemoteAddr().String(), err.Error())) + if !isAlive(client) { + logrus.Debug(fmt.Sprintf("[%s][UDP] client is not alive", client.RemoteAddr().String())) + udpserver.Close() + return + } + } else { + logrus.Error(fmt.Sprintf("[%s][UDP] ReadFromUDP failed: %s", client.RemoteAddr().String(), err.Error())) + } + + continue + } + if clientAddr == nil { + clientAddr = fromAddr + } + + if clientAddr.IP.Equal(fromAddr.IP) && clientAddr.Port == fromAddr.Port { + // from client + atyp := buf[3] + targetAddr := "" + var targetPort uint16 = 0 + var payload []byte + var header []byte + var targetIP net.IP + if atyp == 1 { + isDomain = false + targetAddr = fmt.Sprintf("%d.%d.%d.%d", buf[4], buf[5], buf[6], buf[7]) + targetIP = net.ParseIP(targetAddr) + targetPort = binary.BigEndian.Uint16(buf[8:10]) + payload = buf[10:n] + header = buf[0:10] + } else if atyp == 3 { + isDomain = true + addrLen := int(buf[4]) + targetAddr = string(buf[5 : 5+addrLen]) + targetIPaddr, err := net.ResolveIPAddr("ip", targetAddr) + if err != nil { + logrus.Error(fmt.Sprintf("[%s][UDP] ResolveIPAddr failed: %s", client.RemoteAddr().String(), err.Error())) + continue + } + targetIP = targetIPaddr.IP + targetPort = binary.BigEndian.Uint16(buf[5+addrLen : 5+addrLen+2]) + payload = buf[5+addrLen+2 : n] + header = buf[0 : 5+addrLen+2] + } else if atyp == 4 { + logrus.Error(fmt.Sprintf("[%s][UDP] IPv6: no supported yet", client.RemoteAddr().String())) + continue + } else { + logrus.Error(fmt.Sprintf("[%s][UDP] invalid atyp", client.RemoteAddr().String())) + continue + } + // targetAddrPort := fmt.Sprintf("%s:%d", targetAddr, targetPort) + remoteAddr := &net.UDPAddr{IP: targetIP, Port: int(targetPort)} + udpPortMap[remoteAddr.String()] = make([]byte, len(header)) + copy(udpPortMap[remoteAddr.String()], header) + udpserver.SetWriteDeadline(time.Now().Add(time.Second * 10)) + if _, err = udpserver.WriteToUDP(payload, remoteAddr); err != nil { + logrus.Error(fmt.Sprintf("[%s][UDP] WriteToUDP failed: %s", client.RemoteAddr().String(), err.Error())) + continue + } + } else { + // from remote + fmt.Print(fromAddr.String()) + header := udpPortMap[fromAddr.String()] + if header == nil { + logrus.Error(fmt.Sprintf("[%s][UDP] udpPortMap invalid header", client.RemoteAddr().String())) + continue + } + // header + body + if isDomain { + header = header[0:4] + } + body := append(header, buf[:n]...) + if _, err = udpserver.WriteToUDP(body, clientAddr); err != nil { + logrus.Error(fmt.Sprintf("[%s][UDP] WriteToUDP failed: %s", client.RemoteAddr().String(), err.Error())) + continue + } + } + } +} func Socks5Connect(client net.Conn) (net.Conn, string, error) { buf := make([]byte, 256) @@ -182,7 +274,7 @@ func Socks5Connect(client net.Conn) (net.Conn, string, error) { return nil, "", errors.New("invalid ver") } if cmd == 3 { - return nil, "", errors.New("not support UDP") + return nil, "", errors.New("UDP Associate") } if cmd != 1 { return nil, "", errors.New("invalid cmd, only support connect") diff --git a/install.sh b/install.sh index d02df1a..2bb9f87 100755 --- a/install.sh +++ b/install.sh @@ -20,7 +20,7 @@ ckcmd() { cd /root getcpucore -version=0.2.3 +version=0.3.0 ua3f_tar=ua3f-$version-$cpucore.tar.gz if id -u shellclash >/dev/null 2>&1; then @@ -91,11 +91,12 @@ fi mkdir -p /usr/lib/lua/luci/controller mv controller.lua /usr/lib/lua/luci/controller/ua3f.lua -rm /tmp/luci-modulecache/* >/dev/null 2>&1 -rm /tmp/luci-indexcache* >/dev/null 2>&1 +chmod +x /etc/config/ua3f >/dev/null 2>&1 if [ $? -eq 0 ]; then echo "Install UA3F Success." fi +rm /tmp/luci-modulecache/* >/dev/null 2>&1 +rm /tmp/luci-indexcache* >/dev/null 2>&1 service ua3f.service reload >/dev/null 2>&1 diff --git a/luci/cbi.lua b/luci/cbi.lua index fc08757..b41e0a9 100644 --- a/luci/cbi.lua +++ b/luci/cbi.lua @@ -3,7 +3,7 @@ local uci = require("luci.model.uci").cursor() ua3f = Map("ua3f", "UA3F", [[ - Version: 0.2.3 + Version: 0.3.0
Across the Campus we can reach every corner in the world. ]]