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.
]]