~/Projects/sing
git clone https://code.lsong.org/sing
Commit
- Commit
- 323ace94fda2d86a4580e8854226886ff467c757
- Author
- 世界 <[email protected]>
- Date
- 2022-06-30 17:38:12 +0800 +0800
- Diffstat
common/debug/free.go | 11 - common/redir/mode.go | 9 - common/redir/redir_linux.go | 37 ---- common/redir/redir_other.go | 13 - common/redir/tproxy_linux.go | 136 ----------------- common/redir/tproxy_other.go | 30 --- common/rw/writev_windows.go | 10 go.mod | 2 go.sum | 2 | 32 +++ transport/mixed/listener.go | 158 ------------------- transport/mixed/pac.go | 12 - transport/system/sockopt_linux.go | 14 - transport/system/sockopt_other.go | 9 - transport/tcp/listener.go | 121 --------------- transport/tcp/options.go | 11 - transport/udp/listener.go | 264 --------------------------------- transport/udp/options.go | 11 -
Trim repo
diff --git a/common/debug/free.go b/common/debug/free.go deleted file mode 100644 index 619819fc0ab7caa6d3a469e00e6483d87f8700de..0000000000000000000000000000000000000000 --- a/common/debug/free.go +++ /dev/null @@ -1,11 +0,0 @@ -package debug - -import ( - "runtime/debug" -) - -func Free() { - if Enabled { - debug.FreeOSMemory() - } -} diff --git a/common/redir/mode.go b/common/redir/mode.go deleted file mode 100644 index a265ab74d1acc99207a4a74a748ea3c540470214..0000000000000000000000000000000000000000 --- a/common/redir/mode.go +++ /dev/null @@ -1,9 +0,0 @@ -package redir - -type TransproxyMode uint8 - -const ( - ModeDisabled TransproxyMode = iota - ModeRedirect - ModeTProxy -) diff --git a/common/redir/redir_linux.go b/common/redir/redir_linux.go deleted file mode 100644 index abb1b1a75f03f67591232bbc2f6cf0f37e235035..0000000000000000000000000000000000000000 --- a/common/redir/redir_linux.go +++ /dev/null @@ -1,37 +0,0 @@ -package redir - -import ( - "net" - "net/netip" - "syscall" - - M "github.com/sagernet/sing/common/metadata" -) - -func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) { - rawConn, err := conn.(syscall.Conn).SyscallConn() - if err != nil { - return - } - var rawFd uintptr - err = rawConn.Control(func(fd uintptr) { - rawFd = fd - }) - if err != nil { - return - } - const SO_ORIGINAL_DST = 80 - if conn.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { - raw, err := syscall.GetsockoptIPv6Mreq(int(rawFd), syscall.IPPROTO_IP, SO_ORIGINAL_DST) - if err != nil { - return netip.AddrPort{}, err - } - return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil - } else { - raw, err := syscall.GetsockoptIPv6MTUInfo(int(rawFd), syscall.IPPROTO_IPV6, SO_ORIGINAL_DST) - if err != nil { - return netip.AddrPort{}, err - } - return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), raw.Addr.Port), nil - } -} diff --git a/common/redir/redir_other.go b/common/redir/redir_other.go deleted file mode 100644 index a24a2702b22d76ad8de1e308ebdc24f2e1a53b8b..0000000000000000000000000000000000000000 --- a/common/redir/redir_other.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !linux - -package redir - -import ( - "errors" - "net" - "net/netip" -) - -func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) { - return netip.AddrPort{}, errors.New("unsupported platform") -} diff --git a/common/redir/tproxy_linux.go b/common/redir/tproxy_linux.go deleted file mode 100644 index 0984332415953c19a906ed071cb907d551d15589..0000000000000000000000000000000000000000 --- a/common/redir/tproxy_linux.go +++ /dev/null @@ -1,136 +0,0 @@ -package redir - -import ( - "encoding/binary" - "net" - "net/netip" - "os" - "strconv" - "syscall" - - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - "golang.org/x/sys/unix" -) - -func TProxy(fd uintptr, isIPv6 bool) error { - err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) - if err != nil { - return err - } - if isIPv6 { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) - } - return err -} - -func TProxyUDP(fd uintptr, isIPv6 bool) error { - err := syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) - if err != nil { - return err - } - return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) -} - -func FWMark(fd uintptr, mark int) error { - return syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) -} - -func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { - controlMessages, err := unix.ParseSocketControlMessage(oob) - if err != nil { - return netip.AddrPort{}, err - } - for _, message := range controlMessages { - if message.Header.Level == unix.SOL_IP && message.Header.Type == unix.IP_RECVORIGDSTADDR { - return netip.AddrPortFrom(M.AddrFromIP(message.Data[4:8]), binary.BigEndian.Uint16(message.Data[2:4])), nil - } else if message.Header.Level == unix.SOL_IPV6 && message.Header.Type == unix.IPV6_RECVORIGDSTADDR { - return netip.AddrPortFrom(M.AddrFromIP(message.Data[8:24]), binary.BigEndian.Uint16(message.Data[2:4])), nil - } - } - return netip.AddrPort{}, E.New("not found") -} - -func DialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { - rSockAddr, err := udpAddrToSockAddr(rAddr) - if err != nil { - return nil, err - } - - lSockAddr, err := udpAddrToSockAddr(lAddr) - if err != nil { - return nil, err - } - - fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0) - if err != nil { - return nil, err - } - - if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { - syscall.Close(fd) - return nil, err - } - - if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { - syscall.Close(fd) - return nil, err - } - - if err = syscall.Bind(fd, lSockAddr); err != nil { - syscall.Close(fd) - return nil, err - } - - if err = syscall.Connect(fd, rSockAddr); err != nil { - syscall.Close(fd) - return nil, err - } - - fdFile := os.NewFile(uintptr(fd), F.ToString("net-udp-dial-", rAddr)) - defer fdFile.Close() - - c, err := net.FileConn(fdFile) - if err != nil { - syscall.Close(fd) - return nil, err - } - - return c.(*net.UDPConn), nil -} - -func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { - switch { - case addr.IP.To4() != nil: - ip := [4]byte{} - copy(ip[:], addr.IP.To4()) - - return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil - - default: - ip := [16]byte{} - copy(ip[:], addr.IP.To16()) - - zoneID, err := strconv.ParseUint(addr.Zone, 10, 32) - if err != nil { - zoneID = 0 - } - - return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil - } -} - -func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { - switch net[len(net)-1] { - case '4': - return syscall.AF_INET - case '6': - return syscall.AF_INET6 - } - - if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { - return syscall.AF_INET - } - return syscall.AF_INET6 -} diff --git a/common/redir/tproxy_other.go b/common/redir/tproxy_other.go deleted file mode 100644 index 26778e6b85e2fdf93662b027cf5cb74691947275..0000000000000000000000000000000000000000 --- a/common/redir/tproxy_other.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build !linux - -package redir - -import ( - "net" - "net/netip" - - E "github.com/sagernet/sing/common/exceptions" -) - -func TProxy(fd uintptr, isIPv6 bool) error { - return E.New("only available on linux") -} - -func TProxyUDP(fd uintptr, isIPv6 bool) error { - return E.New("only available on linux") -} - -func FWMark(fd uintptr, mark int) error { - return E.New("only available on linux") -} - -func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { - return netip.AddrPort{}, E.New("only available on linux") -} - -func DialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { - return nil, E.New("only available on linux") -} diff --git a/common/rw/writev_windows.go b/common/rw/writev_windows.go index d053450d9fee24bc4e88c5aad8c1f723167edc6d..724d4f4ab2de71e20e12739eb3c99b9f18dd1c62 100644 --- a/common/rw/writev_windows.go +++ b/common/rw/writev_windows.go @@ -1,17 +1,19 @@ package rw -import "golang.org/x/sys/windows" +import ( + "syscall" +) func WriteV(fd uintptr, data ...[]byte) (int, error) { var n uint32 - buffers := make([]*windows.WSABuf, len(data)) + buffers := make([]*syscall.WSABuf, len(data)) for i, buf := range data { - buffers[i] = &windows.WSABuf{ + buffers[i] = &syscall.WSABuf{ Len: uint32(len(buf)), Buf: &buf[0], } } + package rw -import "golang.org/x/sys/windows" return int(n), err } diff --git a/go.mod b/go.mod index d6bd075dcbb9c69c7026cfd23fd0c632ce9d39af..219df7701232b214eba5070b56761d760bc5c2bb 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/sagernet/sing go 1.18 - -require golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b diff --git a/go.sum b/go.sum index a2987a26fdd170eb1355dd264094829ddfd78e6b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/protocol/http/handshake.go b/protocol/http/handshake.go new file mode 100644 index 0000000000000000000000000000000000000000..b0603d14b50c2c0a0ae923eee054510e47044ff6 --- /dev/null +++ b/protocol/http/handshake.go @@ -0,0 +1,200 @@ +package http + +import ( + std_bufio "bufio" + "context" + "encoding/base64" + "net" + "net/http" + "strings" + "time" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type Handler interface { + N.TCPConnectionHandler + N.UDPConnectionHandler +} + +func HandleConnection(ctx context.Context, conn net.Conn, authenticator auth.Authenticator, handler Handler, metadata M.Metadata) error { + reader := std_bufio.NewReader(conn) + request, err := http.ReadRequest(reader) + if err != nil { + return E.Cause(err, "read http request") + } + if reader.Buffered() > 0 { + _buffer := buf.StackNewSize(reader.Buffered()) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + _, err = buffer.ReadFullFrom(reader, reader.Buffered()) + if err != nil { + return err + } + conn = bufio.NewCachedConn(conn, buffer) + } + return HandleRequest(ctx, request, conn, authenticator, handler, metadata) +} + +func HandleRequest(ctx context.Context, request *http.Request, conn net.Conn, authenticator auth.Authenticator, handler Handler, metadata M.Metadata) error { + var httpClient *http.Client + for { + if authenticator != nil { + var authOk bool + authorization := request.Header.Get("Proxy-Authorization") + if strings.HasPrefix(authorization, "BASIC ") { + userPassword, _ := base64.URLEncoding.DecodeString(authorization[6:]) + userPswdArr := strings.SplitN(string(userPassword), ":", 2) + authOk = authenticator.Verify(userPswdArr[0], userPswdArr[1]) + } + if !authOk { + err := responseWith(request, http.StatusProxyAuthRequired).Write(conn) + if err != nil { + return err + } + } + } + + if request.Method == "CONNECT" { + portStr := request.URL.Port() + if portStr == "" { + portStr = "80" + } + destination := M.ParseSocksaddrHostPortStr(request.URL.Hostname(), portStr) + _, err := conn.Write([]byte(F.ToString("HTTP/", request.ProtoMajor, ".", request.ProtoMinor, " 200 Connection established\r\n\r\n"))) + if err != nil { + return E.Cause(err, "write http response") + } + metadata.Protocol = "http" + metadata.Destination = destination + return handler.NewConnection(ctx, conn, metadata) + } + + keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" + + host := request.Header.Get("Host") + if host != "" { + request.Host = host + } + + request.RequestURI = "" + + removeHopByHopHeaders(request.Header) + removeExtraHTTPHostPort(request) + + if request.URL.Scheme == "" || request.URL.Host == "" { + return responseWith(request, http.StatusBadRequest).Write(conn) + } + + var innerErr error + if httpClient == nil { + httpClient = &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + DialContext: func(context context.Context, network, address string) (net.Conn, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, E.New("unsupported network ", network) + } + metadata.Destination = M.ParseSocksaddr(address) + metadata.Protocol = "http" + left, right := net.Pipe() + go func() { + err := handler.NewConnection(ctx, right, metadata) + if err != nil { + innerErr = err + common.Close(left, right) + } + }() + return left, nil + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + } + + response, err := httpClient.Do(request) + if err != nil { + return common.AnyError(innerErr, err, responseWith(request, http.StatusBadGateway).Write(conn)) + } + + removeHopByHopHeaders(response.Header) + + if keepAlive { + response.Header.Set("Proxy-Connection", "keep-alive") + response.Header.Set("Connection", "keep-alive") + response.Header.Set("Keep-Alive", "timeout=4") + } + + response.Close = !keepAlive + + err = response.Write(conn) + if err != nil { + return common.AnyError(innerErr, err) + } + + if !keepAlive { + return conn.Close() + } + } +} + +func removeHopByHopHeaders(header http.Header) { + // Strip hop-by-hop header based on RFC: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 + // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do + + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") + header.Del("TE") + header.Del("Trailers") + header.Del("Transfer-Encoding") + header.Del("Upgrade") + + connections := header.Get("Connection") + header.Del("Connection") + if len(connections) == 0 { + return + } + for _, h := range strings.Split(connections, ",") { + header.Del(strings.TrimSpace(h)) + } +} + +func removeExtraHTTPHostPort(req *http.Request) { + host := req.Host + if host == "" { + host = req.URL.Host + } + + if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" { + host = pHost + } + + req.Host = host + req.URL.Host = host +} + +func responseWith(request *http.Request, statusCode int) *http.Response { + return &http.Response{ + StatusCode: statusCode, + Status: http.StatusText(statusCode), + Proto: request.Proto, + ProtoMajor: request.ProtoMajor, + ProtoMinor: request.ProtoMinor, + Header: http.Header{}, + } +} diff --git a/protocol/http/listener.go b/protocol/http/listener.go deleted file mode 100644 index 96b6dd9efb4c885830b3703cf3b7539abdecc46d..0000000000000000000000000000000000000000 --- a/protocol/http/listener.go +++ /dev/null @@ -1,178 +0,0 @@ -package http - -import ( - "context" - "encoding/base64" - "net" - "net/http" - "strings" - "time" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Handler interface { - N.TCPConnectionHandler - N.UDPConnectionHandler - E.Handler -} - -func HandleRequest(ctx context.Context, request *http.Request, conn net.Conn, authenticator auth.Authenticator, handler Handler, metadata M.Metadata) error { - var httpClient *http.Client - for { - if authenticator != nil { - var authOk bool - authorization := request.Header.Get("Proxy-Authorization") - if strings.HasPrefix(authorization, "BASIC ") { - userPassword, _ := base64.URLEncoding.DecodeString(authorization[6:]) - userPswdArr := strings.SplitN(string(userPassword), ":", 2) - authOk = authenticator.Verify(userPswdArr[0], userPswdArr[1]) - } - if !authOk { - err := responseWith(request, http.StatusProxyAuthRequired).Write(conn) - if err != nil { - return err - } - } - } - - if request.Method == "CONNECT" { - portStr := request.URL.Port() - if portStr == "" { - portStr = "80" - } - destination := M.ParseSocksaddrHostPortStr(request.URL.Hostname(), portStr) - _, err := conn.Write([]byte(F.ToString("HTTP/", request.ProtoMajor, ".", request.ProtoMinor, " 200 Connection established\r\n\r\n"))) - if err != nil { - return E.Cause(err, "write http response") - } - metadata.Protocol = "http" - metadata.Destination = destination - return handler.NewConnection(ctx, conn, metadata) - } - - keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" - - host := request.Header.Get("Host") - if host != "" { - request.Host = host - } - - request.RequestURI = "" - - removeHopByHopHeaders(request.Header) - removeExtraHTTPHostPort(request) - - if request.URL.Scheme == "" || request.URL.Host == "" { - return responseWith(request, http.StatusBadRequest).Write(conn) - } - - if httpClient == nil { - httpClient = &http.Client{ - Transport: &http.Transport{ - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - DialContext: func(context context.Context, network, address string) (net.Conn, error) { - if network != "tcp" && network != "tcp4" && network != "tcp6" { - return nil, E.New("unsupported network ", network) - } - metadata.Destination = M.ParseSocksaddr(address) - metadata.Protocol = "http" - left, right := net.Pipe() - go func() { - err := handler.NewConnection(ctx, right, metadata) - if err != nil { - common.Close(left, right) - handler.HandleError(err) - } - }() - return left, nil - }, - }, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - } - - response, err := httpClient.Do(request) - if err != nil { - handler.HandleError(err) - return responseWith(request, http.StatusBadGateway).Write(conn) - } - - removeHopByHopHeaders(response.Header) - - if keepAlive { - response.Header.Set("Proxy-Connection", "keep-alive") - response.Header.Set("Connection", "keep-alive") - response.Header.Set("Keep-Alive", "timeout=4") - } - - response.Close = !keepAlive - - err = response.Write(conn) - if err != nil { - return err - } - - if !keepAlive { - return conn.Close() - } - } -} - -func removeHopByHopHeaders(header http.Header) { - // Strip hop-by-hop header based on RFC: - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 - // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do - - header.Del("Proxy-Connection") - header.Del("Proxy-Authenticate") - header.Del("Proxy-Authorization") - header.Del("TE") - header.Del("Trailers") - header.Del("Transfer-Encoding") - header.Del("Upgrade") - - connections := header.Get("Connection") - header.Del("Connection") - if len(connections) == 0 { - return - } - for _, h := range strings.Split(connections, ",") { - header.Del(strings.TrimSpace(h)) - } -} - -func removeExtraHTTPHostPort(req *http.Request) { - host := req.Host - if host == "" { - host = req.URL.Host - } - - if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" { - host = pHost - } - - req.Host = host - req.URL.Host = host -} - -func responseWith(request *http.Request, statusCode int) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Status: http.StatusText(statusCode), - Proto: request.Proto, - ProtoMajor: request.ProtoMajor, - ProtoMinor: request.ProtoMinor, - Header: http.Header{}, - } -} diff --git a/transport/mixed/listener.go b/transport/mixed/listener.go deleted file mode 100644 index e804901bfeed78002fdb76d37710f17806162391..0000000000000000000000000000000000000000 --- a/transport/mixed/listener.go +++ /dev/null @@ -1,158 +0,0 @@ -package mixed - -import ( - std_bufio "bufio" - "context" - "io" - "net" - netHttp "net/http" - "net/netip" - "strings" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/redir" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/common/udpnat" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" - "github.com/sagernet/sing/transport/tcp" - "github.com/sagernet/sing/transport/udp" -) - -type Handler interface { - socks.Handler -} - -type Listener struct { - TCPListener *tcp.Listener - UDPListener *udp.Listener - bindAddr netip.Addr - handler Handler - authenticator auth.Authenticator - udpNat *udpnat.Service[netip.AddrPort] -} - -func NewListener(bind netip.AddrPort, authenticator auth.Authenticator, transproxy redir.TransproxyMode, udpTimeout int64, handler Handler) *Listener { - listener := &Listener{ - bindAddr: bind.Addr(), - handler: handler, - authenticator: authenticator, - } - - listener.TCPListener = tcp.NewTCPListener(bind, listener, tcp.WithTransproxyMode(transproxy)) - if transproxy == redir.ModeTProxy { - listener.UDPListener = udp.NewUDPListener(bind, listener, udp.WithTransproxyMode(transproxy)) - listener.udpNat = udpnat.New[netip.AddrPort](udpTimeout, handler) - } - return listener -} - -func (l *Listener) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - if metadata.Destination.IsValid() { - return l.handler.NewConnection(ctx, conn, metadata) - } - headerType, err := rw.ReadByte(conn) - if err != nil { - return err - } - switch headerType { - case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, l.authenticator, l.handler, metadata) - } - - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - request, err := http.ReadRequest(reader) - if err != nil { - return E.Cause(err, "read http request") - } - - if request.Method == "GET" && request.URL.Path == "/proxy.pac" { - content := newPAC(M.AddrPortFromNet(conn.LocalAddr())) - response := &netHttp.Response{ - StatusCode: 200, - Status: netHttp.StatusText(200), - Proto: request.Proto, - ProtoMajor: request.ProtoMajor, - ProtoMinor: request.ProtoMinor, - Header: netHttp.Header{ - "Content-Type": {"application/x-ns-proxy-autoconfig"}, - }, - ContentLength: int64(len(content)), - Body: io.NopCloser(strings.NewReader(content)), - } - err = response.Write(conn) - if err != nil { - return E.Cause(err, "write pac response") - } - return nil - } - - if reader.Buffered() > 0 { - _buffer := buf.StackNewSize(reader.Buffered()) - defer common.KeepAlive(_buffer) - buffer := common.Dup(_buffer) - defer buffer.Release() - _, err = buffer.ReadFullFrom(reader, reader.Buffered()) - if err != nil { - return err - } - - conn = bufio.NewCachedConn(conn, buffer) - } - - return http.HandleRequest(ctx, request, conn, l.authenticator, l.handler, metadata) -} - -func (l *Listener) WriteIsThreadUnsafe() { -} - -func (l *Listener) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata M.Metadata) error { - l.udpNat.NewPacket(ctx, metadata.Source.AddrPort(), buffer, metadata, func(netConn N.PacketConn) N.PacketWriter { - return &tproxyPacketWriter{conn} - }) - return nil -} - -type tproxyPacketWriter struct { - source N.PacketConn -} - -func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - udpConn, err := redir.DialUDP("udp", destination.UDPAddr(), M.SocksaddrFromNet(w.source.LocalAddr()).UDPAddr()) - if err != nil { - return E.Cause(err, "tproxy udp write back") - } - defer udpConn.Close() - return common.Error(udpConn.Write(buffer.Bytes())) -} - -func (l *Listener) HandleError(err error) { - l.handler.HandleError(err) -} - -func (l *Listener) Start() error { - err := l.TCPListener.Start() - if err != nil { - return err - } - if l.UDPListener != nil { - err = l.UDPListener.Start() - } - return err -} - -func (l *Listener) Close() error { - return common.Close( - l.TCPListener, - l.UDPListener, - ) -} diff --git a/transport/mixed/pac.go b/transport/mixed/pac.go deleted file mode 100644 index 7835d254ef18f6d1b45866f410d5791d8bebc43a..0000000000000000000000000000000000000000 --- a/transport/mixed/pac.go +++ /dev/null @@ -1,12 +0,0 @@ -package mixed - -import ( - "net/netip" -) - -func newPAC(proxyAddr netip.AddrPort) string { - return ` -function FindProxyForURL(url, host) { - return "SOCKS5 ` + proxyAddr.String() + `; PROXY ` + proxyAddr.String() + `"; -}` -} diff --git a/transport/system/sockopt_linux.go b/transport/system/sockopt_linux.go deleted file mode 100644 index 2f7335a174a64e1de94f74584555fbf274cc2f4a..0000000000000000000000000000000000000000 --- a/transport/system/sockopt_linux.go +++ /dev/null @@ -1,14 +0,0 @@ -package system - -import ( - "syscall" -) - -const ( - TCP_FASTOPEN = 23 - TCP_FASTOPEN_CONNECT = 30 -) - -func TCPFastOpen(fd uintptr) error { - return syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN_CONNECT, 1) -} diff --git a/transport/system/sockopt_other.go b/transport/system/sockopt_other.go deleted file mode 100644 index 804f39dd52c19b8029d9eeaae23401dcfb63a7c3..0000000000000000000000000000000000000000 --- a/transport/system/sockopt_other.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !linux - -package system - -import "github.com/sagernet/sing/common/exceptions" - -func TCPFastOpen(fd uintptr) error { - return exceptions.New("only available on linux") -} diff --git a/transport/tcp/listener.go b/transport/tcp/listener.go deleted file mode 100644 index 3ae4a2d93d986ee496f7cf0821efb08b74bed29e..0000000000000000000000000000000000000000 --- a/transport/tcp/listener.go +++ /dev/null @@ -1,121 +0,0 @@ -package tcp - -import ( - "context" - "net" - "net/netip" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/debug" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/redir" -) - -type Handler interface { - N.TCPConnectionHandler - E.Handler -} - -type Listener struct { - bind netip.AddrPort - handler Handler - trans redir.TransproxyMode - lAddr *net.TCPAddr - *net.TCPListener -} - -func NewTCPListener(listen netip.AddrPort, handler Handler, options ...Option) *Listener { - listener := &Listener{ - bind: listen, - handler: handler, - } - for _, option := range options { - option(listener) - } - return listener -} - -func (l *Listener) Start() error { - tcpListener, err := net.ListenTCP(M.NetworkFromNetAddr("tcp", l.bind.Addr()), net.TCPAddrFromAddrPort(l.bind)) - if err != nil { - return err - } - if l.trans == redir.ModeTProxy { - l.lAddr = tcpListener.Addr().(*net.TCPAddr) - fd, err := common.GetFileDescriptor(tcpListener) - if err != nil { - return err - } - err = redir.TProxy(fd, l.bind.Addr().Is6()) - if err != nil { - return E.Cause(err, "configure tproxy") - } - } - l.TCPListener = tcpListener - go l.loop() - return nil -} - -func (l *Listener) Close() error { - if l == nil || l.TCPListener == nil { - return nil - } - return l.TCPListener.Close() -} - -func (l *Listener) loop() { - for { - tcpConn, err := l.AcceptTCP() - if err != nil { - l.handler.HandleError(E.New("tcp listener closed: ", err)) - l.Close() - return - } - metadata := M.Metadata{ - Source: M.SocksaddrFromNet(tcpConn.RemoteAddr()), - } - switch l.trans { - case redir.ModeRedirect: - destination, err := redir.GetOriginalDestination(tcpConn) - if err == nil { - metadata.Protocol = "redirect" - metadata.Destination = M.SocksaddrFromNetIP(destination) - } - case redir.ModeTProxy: - lAddr := tcpConn.LocalAddr().(*net.TCPAddr) - rAddr := tcpConn.RemoteAddr().(*net.TCPAddr) - - if lAddr.Port != l.lAddr.Port || !lAddr.IP.Equal(rAddr.IP) && !lAddr.IP.IsLoopback() && !lAddr.IP.IsPrivate() { - metadata.Protocol = "tproxy" - metadata.Destination = M.SocksaddrFromNet(lAddr) - } - } - go func() { - metadata.Protocol = "tcp" - hErr := l.handler.NewConnection(context.Background(), tcpConn, metadata) - if hErr != nil { - l.handler.HandleError(hErr) - } - debug.Free() - }() - } -} - -type Error struct { - Conn net.Conn - Cause error -} - -func (e *Error) Error() string { - return e.Cause.Error() -} - -func (e *Error) Unwrap() error { - return e.Cause -} - -func (e *Error) Close() error { - return common.Close(e.Conn) -} diff --git a/transport/tcp/options.go b/transport/tcp/options.go deleted file mode 100644 index 7d12a18cbad21271e5255603f7978d9e4a8ca990..0000000000000000000000000000000000000000 --- a/transport/tcp/options.go +++ /dev/null @@ -1,11 +0,0 @@ -package tcp - -import "github.com/sagernet/sing/common/redir" - -type Option func(*Listener) - -func WithTransproxyMode(mode redir.TransproxyMode) Option { - return func(listener *Listener) { - listener.trans = mode - } -} diff --git a/transport/udp/listener.go b/transport/udp/listener.go deleted file mode 100644 index b8a5feca536453f2ef1e289c97c354c5e34ed011..0000000000000000000000000000000000000000 --- a/transport/udp/listener.go +++ /dev/null @@ -1,264 +0,0 @@ -package udp - -import ( - "context" - "net" - "net/netip" - "os" - "sync" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/redir" -) - -type Handler interface { - N.UDPHandler - E.Handler -} - -type Listener struct { - *net.UDPConn - handler Handler - bind netip.AddrPort - tproxy bool - forceAddr6 bool - access sync.RWMutex - closed chan struct{} - outbound chan *outboundPacket -} - -func (l *Listener) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := l.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (l *Listener) WriteIsThreadUnsafe() { -} - -func (l *Listener) loopBack() { - for { - select { - case packet := <-l.outbound: - err := l.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - l.handler.HandleError(E.New("udp write failed: ", err)) - } - continue - case <-l.closed: - } - for { - select { - case packet := <-l.outbound: - packet.buffer.Release() - default: - return - } - } - } -} - -type outboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (l *Listener) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - l.access.RLock() - defer l.access.RUnlock() - - select { - case <-l.closed: - return os.ErrClosed - default: - } - - l.outbound <- &outboundPacket{buffer, destination} - return nil -} - -func (l *Listener) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - if destination.Family().IsFqdn() { - udpAddr, err := net.ResolveUDPAddr("udp", destination.String()) - if err != nil { - return err - } - return common.Error(l.UDPConn.WriteTo(buffer.Bytes(), udpAddr)) - } - if l.forceAddr6 && destination.Addr.Is4() { - destination.Addr = netip.AddrFrom16(destination.Addr.As16()) - } - return common.Error(l.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -func NewUDPListener(listen netip.AddrPort, handler Handler, options ...Option) *Listener { - listener := &Listener{ - handler: handler, - bind: listen, - outbound: make(chan *outboundPacket), - closed: make(chan struct{}), - } - for _, option := range options { - option(listener) - } - return listener -} - -func (l *Listener) Start() error { - udpConn, err := net.ListenUDP(M.NetworkFromNetAddr("udp", l.bind.Addr()), net.UDPAddrFromAddrPort(l.bind)) - if err != nil { - return err - } - l.forceAddr6 = l.bind.Addr().Is6() - - if l.tproxy { - fd, err := common.GetFileDescriptor(udpConn) - if err != nil { - return err - } - err = redir.TProxy(fd, l.bind.Addr().Is6()) - if err != nil { - return E.Cause(err, "configure tproxy") - } - err = redir.TProxyUDP(fd, l.bind.Addr().Is6()) - if err != nil { - return E.Cause(err, "configure tproxy") - } - } - - l.UDPConn = udpConn - - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](l.handler); threadUnsafeHandler { - go l.loopThreadSafe() - } else { - go l.loop() - } - - go l.loopBack() - return nil -} - -func (l *Listener) Close() error { - if l == nil || l.UDPConn == nil { - return nil - } - return l.UDPConn.Close() -} - -func (l *Listener) loop() { - defer close(l.closed) - - _buffer := buf.StackNewPacket() - defer common.KeepAlive(_buffer) - buffer := common.Dup(_buffer) - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - if !l.tproxy { - for { - buffer.Reset() - n, addr, err := l.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - l.handler.HandleError(E.New("udp listener closed: ", err)) - return - } - buffer.Truncate(n) - err = l.handler.NewPacket(context.Background(), l, buffer, M.Metadata{ - Protocol: "udp", - Source: M.SocksaddrFromNetIP(addr), - }) - if err != nil { - l.handler.HandleError(err) - } - } - } else { - _oob := make([]byte, 1024) - defer common.KeepAlive(_oob) - oob := common.Dup(_oob) - for { - buffer.Reset() - n, oobN, _, addr, err := l.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - l.handler.HandleError(E.New("udp listener closed: ", err)) - return - } - buffer.Truncate(n) - destination, err := redir.GetOriginalDestinationFromOOB(oob[:oobN]) - if err != nil { - l.handler.HandleError(E.Cause(err, "get original destination")) - continue - } - buffer.Resize(buf.ReversedHeader, n) - err = l.handler.NewPacket(context.Background(), l, buffer, M.Metadata{ - Protocol: "tproxy", - Source: M.SocksaddrFromNetIP(addr), - Destination: M.SocksaddrFromNetIP(destination), - }) - if err != nil { - l.handler.HandleError(err) - } - } - } -} - -func (l *Listener) loopThreadSafe() { - defer close(l.closed) - - if !l.tproxy { - for { - buffer := buf.NewPacket() - n, addr, err := l.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - buffer.Release() - l.handler.HandleError(E.New("udp listener closed: ", err)) - return - } - buffer.Truncate(n) - err = l.handler.NewPacket(context.Background(), l, buffer, M.Metadata{ - Protocol: "udp", - Source: M.SocksaddrFromNetIP(addr), - }) - if err != nil { - buffer.Release() - l.handler.HandleError(err) - } - } - } else { - _oob := make([]byte, 1024) - defer common.KeepAlive(_oob) - oob := common.Dup(_oob) - for { - buffer := buf.NewPacket() - n, oobN, _, addr, err := l.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - l.handler.HandleError(E.New("udp listener closed: ", err)) - return - } - buffer.Truncate(n) - destination, err := redir.GetOriginalDestinationFromOOB(oob[:oobN]) - if err != nil { - buffer.Release() - l.handler.HandleError(E.Cause(err, "get original destination")) - continue - } - buffer.Resize(buf.ReversedHeader, n) - err = l.handler.NewPacket(context.Background(), l, buffer, M.Metadata{ - Protocol: "tproxy", - Source: M.SocksaddrFromNetIP(addr), - Destination: M.SocksaddrFromNetIP(destination), - }) - if err != nil { - buffer.Release() - l.handler.HandleError(err) - } - } - } -} diff --git a/transport/udp/options.go b/transport/udp/options.go deleted file mode 100644 index 2dab2208b38657eee7c938665bad285c00a2094a..0000000000000000000000000000000000000000 --- a/transport/udp/options.go +++ /dev/null @@ -1,11 +0,0 @@ -package udp - -import "github.com/sagernet/sing/common/redir" - -type Option func(*Listener) - -func WithTransproxyMode(mode redir.TransproxyMode) Option { - return func(listener *Listener) { - listener.tproxy = mode == redir.ModeTProxy - } -}