Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 协议流量。
Expand Down
64 changes: 64 additions & 0 deletions redirector/redirector.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -20,6 +23,7 @@ type Redirection struct {
Dial
RedirectTo net.Addr
InboundConn net.Conn
ClientIP string
}

type Redirector struct {
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions tunnel/tls/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions tunnel/trojan/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions tunnel/websocket/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand All @@ -70,19 +72,23 @@ 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)
}
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || req.URL.Path != s.path {
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)
}
Expand Down