diff --git a/README.md b/README.md index 7a53885c1..64141fd4c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Trojan-Go [![Go Report Card](https://goreportcard.com/badge/github.com/p4gefau1t/trojan-go)](https://goreportcard.com/report/github.com/p4gefau1t/trojan-go) [![Downloads](https://img.shields.io/github/downloads/p4gefau1t/trojan-go/total?label=downloads&logo=github&style=flat-square)](https://img.shields.io/github/downloads/p4gefau1t/trojan-go/total?label=downloads&logo=github&style=flat-square) +> 本 fork 重点更新: 实现转发真实的客户端 IP 地址到后续的反代服务器 + 使用 Go 实现的完整 Trojan 代理,兼容原版 Trojan 协议及配置文件格式。安全、高效、轻巧、易用。 Trojan-Go 支持[多路复用](#多路复用)提升并发性能;使用[路由模块](#路由模块)实现国内外分流;支持 [CDN 流量中转](#Websocket)(基于 WebSocket over TLS);支持使用 AEAD 对 Trojan 流量进行[二次加密](#aead-加密)(基于 Shadowsocks AEAD);支持可插拔的[传输层插件](#传输层插件),允许替换 TLS,使用其他加密隧道传输 Trojan 协议流量。 diff --git a/redirector/redirector.go b/redirector/redirector.go index ae4068bee..2e15821cb 100644 --- a/redirector/redirector.go +++ b/redirector/redirector.go @@ -1,10 +1,13 @@ package redirector import ( + "bytes" "context" + "fmt" "io" "net" "reflect" + "strings" "github.com/p4gefau1t/trojan-go/common" "github.com/p4gefau1t/trojan-go/log" @@ -20,6 +23,7 @@ type Redirection struct { Dial RedirectTo net.Addr InboundConn net.Conn + ClientIP string } type Redirector struct { @@ -36,6 +40,61 @@ func (r *Redirector) Redirect(redirection *Redirection) { } } +func injectForwardedHeader(inbound net.Conn, outbound net.Conn, clientIP string) error { + var headerBuf bytes.Buffer + buf := make([]byte, 4096) + + for { + n, err := inbound.Read(buf) + if err != nil { + return err + } + headerBuf.Write(buf[:n]) + + if bytes.Contains(headerBuf.Bytes(), []byte("\r\n\r\n")) { + break + } + + if headerBuf.Len() > 65536 { + return fmt.Errorf("headers too large") + } + } + + headerBytes := headerBuf.Bytes() + idx := bytes.Index(headerBytes, []byte("\r\n\r\n")) + + headers := headerBytes[:idx] + remaining := headerBytes[idx+4:] + + headerStr := string(headers) + lines := strings.Split(headerStr, "\r\n") + + xffFound := false + for i, line := range lines { + if strings.HasPrefix(strings.ToLower(line), "x-forwarded-for:") { + lines[i] = line + ", " + clientIP + xffFound = true + break + } + } + if !xffFound { + lines = append(lines, "X-Forwarded-For: "+clientIP) + } + + lines = append(lines, "X-Real-IP: "+clientIP) + + var out bytes.Buffer + for _, line := range lines { + out.WriteString(line) + out.WriteString("\r\n") + } + out.WriteString("\r\n") + out.Write(remaining) + + _, err := outbound.Write(out.Bytes()) + return err +} + func (r *Redirector) worker() { for { select { @@ -60,6 +119,11 @@ func (r *Redirector) worker() { return } defer outboundConn.Close() + if redirection.ClientIP != "" { + if err := injectForwardedHeader(redirection.InboundConn, outboundConn, redirection.ClientIP); err != nil { + log.Debug("failed to inject X-Forwarded-For header, using plain TCP forwarding:", err) + } + } errChan := make(chan error, 2) copyConn := func(a, b net.Conn) { _, err := io.Copy(a, b) diff --git a/tunnel/tls/server.go b/tunnel/tls/server.go index f9f66d717..e457242ec 100644 --- a/tunnel/tls/server.go +++ b/tunnel/tls/server.go @@ -124,9 +124,11 @@ func (s *Server) acceptLoop() { log.Error(common.NewError("failed to perform tls handshake with " + tlsConn.RemoteAddr().String() + ", redirecting").Base(err)) switch { case s.fallbackAddress != nil: + ip, _, _ := net.SplitHostPort(handshakeRewindConn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ InboundConn: handshakeRewindConn, RedirectTo: s.fallbackAddress, + ClientIP: ip, }) case s.httpResp != nil: handshakeRewindConn.Write(s.httpResp) @@ -162,9 +164,11 @@ func (s *Server) acceptLoop() { if atomic.LoadInt32(&s.nextHTTP) != 1 { // there is no websocket layer waiting for connections, redirect it log.Error("incoming http request, but no websocket server is listening") + ip, _, _ := net.SplitHostPort(rewindConn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ InboundConn: rewindConn, RedirectTo: s.fallbackAddress, + ClientIP: ip, }) return } diff --git a/tunnel/trojan/server.go b/tunnel/trojan/server.go index d30810a17..3a298a08e 100644 --- a/tunnel/trojan/server.go +++ b/tunnel/trojan/server.go @@ -149,9 +149,11 @@ func (s *Server) acceptLoop() { rewindConn.Rewind() rewindConn.StopBuffering() log.Warn(common.NewError("connection with invalid trojan header from " + rewindConn.RemoteAddr().String()).Base(err)) + ip, _, _ := net.SplitHostPort(rewindConn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ RedirectTo: s.redirAddr, InboundConn: rewindConn, + ClientIP: ip, }) return } diff --git a/tunnel/websocket/server.go b/tunnel/websocket/server.go index 9482fcd49..d01644f5d 100644 --- a/tunnel/websocket/server.go +++ b/tunnel/websocket/server.go @@ -55,9 +55,11 @@ func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { return nil, common.NewError("websocket failed to accept connection from underlying server") } if !s.enabled { + ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ InboundConn: conn, RedirectTo: s.redirAddr, + ClientIP: ip, }) return nil, common.NewError("websocket is disabled. redirecting http request from " + conn.RemoteAddr().String()) } @@ -70,9 +72,11 @@ func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { log.Debug("invalid http request") rewindConn.Rewind() rewindConn.StopBuffering() + ip, _, _ := net.SplitHostPort(rewindConn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ InboundConn: rewindConn, RedirectTo: s.redirAddr, + ClientIP: ip, }) return nil, common.NewError("not a valid http request: " + conn.RemoteAddr().String()).Base(err) } @@ -80,9 +84,11 @@ func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { log.Debug("invalid http websocket handshake request") rewindConn.Rewind() rewindConn.StopBuffering() + ip, _, _ := net.SplitHostPort(rewindConn.RemoteAddr().String()) s.redir.Redirect(&redirector.Redirection{ InboundConn: rewindConn, RedirectTo: s.redirAddr, + ClientIP: ip, }) return nil, common.NewError("not a valid websocket handshake request: " + conn.RemoteAddr().String()).Base(err) }