add http proxy, fix bugs

This commit is contained in:
Page Fault
2020-06-16 17:36:19 +00:00
parent 896dad3d38
commit 2d5b2484b3
16 changed files with 510 additions and 45 deletions
+4 -4
View File
@@ -11,19 +11,19 @@ type RewindReader struct {
rawReader io.Reader
buf []byte
bufReadIdx int
rewinded bool
rewound bool
buffering bool
bufferSize int
}
func (r *RewindReader) Read(p []byte) (int, error) {
if r.rewinded {
if r.rewound {
if len(r.buf) > r.bufReadIdx {
n := copy(p, r.buf[r.bufReadIdx:])
r.bufReadIdx += n
return n, nil
}
r.rewinded = false //all buffering content has been read
r.rewound = false //all buffering content has been read
}
n, err := r.rawReader.Read(p)
if r.buffering {
@@ -62,7 +62,7 @@ func (r *RewindReader) Rewind() {
if r.bufferSize == 0 {
panic("has no buffer")
}
r.rewinded = true
r.rewound = true
r.bufReadIdx = 0
}
+21 -4
View File
@@ -4,7 +4,8 @@ import (
"context"
"github.com/p4gefau1t/trojan-go/config"
"github.com/p4gefau1t/trojan-go/proxy"
"github.com/p4gefau1t/trojan-go/tunnel"
"github.com/p4gefau1t/trojan-go/tunnel/adapter"
"github.com/p4gefau1t/trojan-go/tunnel/http"
"github.com/p4gefau1t/trojan-go/tunnel/mux"
"github.com/p4gefau1t/trojan-go/tunnel/router"
"github.com/p4gefau1t/trojan-go/tunnel/shadowsocks"
@@ -43,16 +44,32 @@ func GenerateClientTree(transportPlugin bool, muxEnabled bool, wsEnabled bool, s
func init() {
proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {
cfg := config.FromContext(ctx, Name).(*Config)
serverStack := []string{socks.Name}
transportServer, err := transport.NewServer(ctx, nil)
if err != nil {
return nil, err
}
root := &proxy.Node{
Name: transport.Name,
Next: make(map[string]*proxy.Node),
IsEndpoint: false,
Context: ctx,
Server: transportServer,
}
root.BuildNext(adapter.Name).BuildNext(http.Name).IsEndpoint = true
root.BuildNext(adapter.Name).BuildNext(socks.Name).IsEndpoint = true
clientStack := GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, cfg.Router.Enabled)
c, err := proxy.CreateClientStack(ctx, clientStack)
if err != nil {
return nil, err
}
s, err := proxy.CreateServerStack(ctx, serverStack)
s := proxy.FindAllEndpoints(root)
if err != nil {
return nil, err
}
return proxy.NewProxy(ctx, []tunnel.Server{s}, c), nil
return proxy.NewProxy(ctx, s, c), nil
})
}
+2 -2
View File
@@ -21,7 +21,7 @@ const Name = "SERVER"
func init() {
proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {
cfg := config.FromContext(ctx, Name).(*client.Config)
s, err := transport.NewServer(ctx, nil)
transportServer, err := transport.NewServer(ctx, nil)
if err != nil {
return nil, err
}
@@ -35,7 +35,7 @@ func init() {
Next: make(map[string]*proxy.Node),
IsEndpoint: false,
Context: ctx,
Server: s,
Server: transportServer,
}
if !cfg.TransportPlugin.Enabled {
+4 -2
View File
@@ -23,6 +23,8 @@ import (
_ "net/http/pprof"
)
// test key and cert
var cert = `
-----BEGIN CERTIFICATE-----
MIIDZTCCAk0CFFphZh018B5iAD9F5fV4y0AlD0LxMA0GCSqGSIb3DQEBCwUAMG8x
@@ -293,7 +295,7 @@ transport-plugin:
enabled: true
type: plaintext
shadowsocks:
enabled: false
enabled: true
method: AEAD_CHACHA20_POLY1305
password: 12345678
mux:
@@ -316,7 +318,7 @@ transport-plugin:
enabled: true
type: plaintext
shadowsocks:
enabled: false
enabled: true
method: AEAD_CHACHA20_POLY1305
password: 12345678
websocket:
+101
View File
@@ -0,0 +1,101 @@
package adapter
import (
"context"
"github.com/p4gefau1t/trojan-go/common"
"github.com/p4gefau1t/trojan-go/log"
"github.com/p4gefau1t/trojan-go/tunnel"
"github.com/p4gefau1t/trojan-go/tunnel/http"
"github.com/p4gefau1t/trojan-go/tunnel/socks"
"github.com/p4gefau1t/trojan-go/tunnel/transport"
)
type Server struct {
underlay tunnel.Server
socksConn chan tunnel.Conn
httpConn chan tunnel.Conn
nextSocks bool
ctx context.Context
cancel context.CancelFunc
}
func (s *Server) acceptConnLoop() {
for {
conn, err := s.underlay.AcceptConn(&Tunnel{})
if err != nil {
select {
case <-s.ctx.Done():
log.Debug("exiting")
return
default:
continue
}
}
rewindConn := common.NewRewindConn(conn)
rewindConn.SetBufferSize(16)
buf := [3]byte{}
_, err = rewindConn.Read(buf[:])
rewindConn.Rewind()
rewindConn.StopBuffering()
if err != nil {
log.Error(common.NewError("failed to detect proxy protocol type").Base(err))
continue
}
if buf[0] == 5 && s.nextSocks {
log.Debug("socks5 connection")
s.socksConn <- &transport.Conn{
Conn: rewindConn,
}
} else {
log.Debug("http connection")
s.httpConn <- &transport.Conn{
Conn: rewindConn,
}
}
}
}
func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {
if _, ok := overlay.(*http.Tunnel); ok {
select {
case conn := <-s.httpConn:
return conn, nil
case <-s.ctx.Done():
return nil, common.NewError("adapter closed")
}
} else if _, ok := overlay.(*socks.Tunnel); ok {
s.nextSocks = true
select {
case conn := <-s.socksConn:
return conn, nil
case <-s.ctx.Done():
return nil, common.NewError("adapter closed")
}
} else {
panic("invalid overlay")
}
}
func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
// no packet conn available, but it's ok to stuck here
<-s.ctx.Done()
return nil, common.NewError("adapter server closed")
}
func (s *Server) Close() error {
s.cancel()
return s.underlay.Close()
}
func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {
ctx, cancel := context.WithCancel(ctx)
server := &Server{
underlay: underlay,
socksConn: make(chan tunnel.Conn, 32),
httpConn: make(chan tunnel.Conn, 32),
ctx: ctx,
cancel: cancel,
}
go server.acceptConnLoop()
return server, nil
}
+26
View File
@@ -0,0 +1,26 @@
package adapter
import (
"context"
"github.com/p4gefau1t/trojan-go/tunnel"
)
const Name = "ADAPTER"
type Tunnel struct{}
func (t *Tunnel) Name() string {
return Name
}
func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {
panic("not supported")
}
func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {
return NewServer(ctx, server)
}
func init() {
tunnel.RegisterTunnel(Name, &Tunnel{})
}
+4 -4
View File
@@ -2,7 +2,7 @@ package dokodemo
import (
"context"
"io"
"github.com/p4gefau1t/trojan-go/common"
"net"
"github.com/p4gefau1t/trojan-go/tunnel"
@@ -58,15 +58,15 @@ func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {
n := copy(p, payload)
return n, c.M, nil
case <-c.Ctx.Done():
return 0, nil, io.EOF
return 0, nil, common.NewError("dokodemo packet conn closed")
}
}
func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {
select {
case c.Output <- p:
return len(p), nil
case <-c.Ctx.Done():
return 0, io.EOF
return 0, common.NewError("dokodemo packet conn failed to write")
}
return len(p), nil
}
+89
View File
@@ -0,0 +1,89 @@
package http
import (
"bufio"
"context"
"fmt"
"github.com/p4gefau1t/trojan-go/common"
"github.com/p4gefau1t/trojan-go/config"
"github.com/p4gefau1t/trojan-go/test/util"
"github.com/p4gefau1t/trojan-go/tunnel/transport"
"io/ioutil"
"net"
"net/http"
"testing"
)
func TestHTTP(t *testing.T) {
port := common.PickPort("tcp", "127.0.0.1")
ctx := config.WithConfig(context.Background(), transport.Name, &transport.Config{
LocalHost: "127.0.0.1",
LocalPort: port,
})
tcpServer, err := transport.NewServer(ctx, nil)
common.Must(err)
s, err := NewServer(ctx, tcpServer)
common.Must(err)
for i := 0; i < 10; i++ {
go func() {
http.Get(fmt.Sprintf("http://127.0.0.1:%d", port))
}()
conn, err := s.AcceptConn(nil)
common.Must(err)
bufReader := bufio.NewReader(bufio.NewReader(conn))
req, err := http.ReadRequest(bufReader)
fmt.Println(req)
ioutil.ReadAll(req.Body)
req.Body.Close()
resp := `HTTP/1.1 200 OK
Date: Mon, 19 Jul 2004 16:18:20 GMT
Server: Apache
Last-Modified: Sat, 10 Jul 2004 17:29:19 GMT
ETag: "1d0325-2470-40f0276f"
Accept-Ranges: bytes
Content-Length: 4
Connection: close
Content-Type: text/html
1234
`
_, err = conn.Write([]byte(resp))
common.Must(err)
buf := [100]byte{}
_, err = conn.Read(buf[:])
if err == nil {
t.Fail()
}
conn.Close()
}
req, err := http.NewRequest("CONNECT", "https://google.com:443", nil)
common.Must(err)
conn1, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
common.Must(err)
req.Write(conn1)
conn2, err := s.AcceptConn(nil)
common.Must(err)
if conn2.Metadata().Port != 443 || conn2.Metadata().DomainName != "google.com" {
t.Fail()
}
connResp := "HTTP/1.1 200 Connection established\r\n\r\n"
buf := make([]byte, len(connResp))
conn1.Read(buf)
if string(buf) != connResp {
t.Fail()
}
if !util.CheckConn(conn1, conn2) {
t.Fail()
}
conn1.Close()
conn2.Close()
s.Close()
}
+205
View File
@@ -0,0 +1,205 @@
package http
import (
"bufio"
"context"
"fmt"
"github.com/p4gefau1t/trojan-go/common"
"github.com/p4gefau1t/trojan-go/log"
"github.com/p4gefau1t/trojan-go/tunnel"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
)
type ConnectConn struct {
net.Conn
metadata *tunnel.Metadata
}
func (c *ConnectConn) Metadata() *tunnel.Metadata {
return c.metadata
}
type OtherConn struct {
net.Conn
metadata *tunnel.Metadata //fixed
reqReader *io.PipeReader
respWriter *io.PipeWriter
ctx context.Context
cancel context.CancelFunc
}
func (c *OtherConn) Metadata() *tunnel.Metadata {
return c.metadata
}
func (c *OtherConn) Read(p []byte) (int, error) {
n, err := c.reqReader.Read(p)
if err == io.EOF {
if n != 0 {
panic("non zero")
}
select {
case <-c.ctx.Done():
return 0, common.NewError("http conn closed")
}
}
return n, err
}
func (c *OtherConn) Write(p []byte) (int, error) {
return c.respWriter.Write(p)
}
func (c *OtherConn) Close() error {
c.cancel()
c.reqReader.Close()
c.respWriter.Close()
return nil
}
type Server struct {
underlay tunnel.Server
connChan chan tunnel.Conn
ctx context.Context
cancel context.CancelFunc
}
func (s *Server) acceptLoop() {
for {
conn, err := s.underlay.AcceptConn(&Tunnel{})
if err != nil {
select {
case <-s.ctx.Done():
log.Error(common.NewError("http closed"))
return
default:
log.Error(common.NewError("http failed to accept connection").Base(err))
continue
}
}
go func(conn net.Conn) {
reqBufReader := bufio.NewReader(ioutil.NopCloser(conn))
req, err := http.ReadRequest(reqBufReader)
if err != nil {
log.Error(common.NewError("not a valid http request").Base(err))
return
}
if strings.ToUpper(req.Method) == "CONNECT" { // CONNECT
addr, err := tunnel.NewAddressFromAddr("tcp", req.Host)
if err != nil {
log.Error(common.NewError("invalid http dest address").Base(err))
conn.Close()
return
}
resp := fmt.Sprintf("HTTP/%d.%d 200 Connection established\r\n\r\n", req.ProtoMajor, req.ProtoMinor)
_, err = conn.Write([]byte(resp))
if err != nil {
log.Error("http failed to respond connect request")
conn.Close()
return
}
s.connChan <- &ConnectConn{
Conn: conn,
metadata: &tunnel.Metadata{
Address: addr,
},
}
} else { // GET, POST, PUT...
defer conn.Close()
for {
ctx, cancel := context.WithCancel(s.ctx)
reqReader, reqWriter := io.Pipe()
respReader, respWriter := io.Pipe()
var addr *tunnel.Address
_, strPort, err := net.SplitHostPort(req.Host)
if err != nil {
if strPort == "" { //default port 80
addr = tunnel.NewAddressFromHostPort("tcp", req.Host, 80)
}
} else {
addr, err = tunnel.NewAddressFromAddr("tcp", req.Host)
if err != nil {
log.Error(common.NewError("invalid http dest address").Base(err))
return
}
}
log.Debug("http dest", addr)
newConn := &OtherConn{
Conn: conn,
metadata: &tunnel.Metadata{
Address: addr,
},
ctx: ctx,
cancel: cancel,
reqReader: reqReader,
respWriter: respWriter,
}
s.connChan <- newConn // pass this http session connection to proxy.RelayConn
err = req.Write(reqWriter) // write request to the remote
if err != nil {
log.Error(common.NewError("http failed to write http request").Base(err))
return
}
respBufReader := bufio.NewReader(ioutil.NopCloser(respReader)) // read response from the remote
resp, err := http.ReadResponse(respBufReader, req)
if err != nil {
log.Error(common.NewError("http failed to read http response").Base(err))
return
}
err = resp.Write(conn) // send the response back to the local
if err != nil {
log.Error(common.NewError("http failed to write the response back").Base(err))
return
}
newConn.Close()
req.Body.Close()
resp.Body.Close()
req, err = http.ReadRequest(reqBufReader) //read the next http request from local
if err != nil {
log.Error(common.NewError("http failed to the read request from local").Base(err))
return
}
}
}
}(conn)
}
}
func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {
select {
case conn := <-s.connChan:
return conn, nil
case <-s.ctx.Done():
return nil, common.NewError("http server closed")
}
}
func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
<-s.ctx.Done()
return nil, common.NewError("http server closed")
}
func (s *Server) Close() error {
s.cancel()
return s.underlay.Close()
}
func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {
ctx, cancel := context.WithCancel(ctx)
server := &Server{
underlay: underlay,
connChan: make(chan tunnel.Conn, 32),
ctx: ctx,
cancel: cancel,
}
go server.acceptLoop()
return server, nil
}
+26
View File
@@ -0,0 +1,26 @@
package http
import (
"context"
"github.com/p4gefau1t/trojan-go/tunnel"
)
const Name = "HTTP"
type Tunnel struct{}
func (t *Tunnel) Name() string {
return Name
}
func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {
panic("not supported")
}
func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {
return NewServer(ctx, server)
}
func init() {
tunnel.RegisterTunnel(Name, &Tunnel{})
}
+1 -1
View File
@@ -15,7 +15,7 @@ type Client struct {
}
func (c *Client) DialConn(address *tunnel.Address, tunnel tunnel.Tunnel) (tunnel.Conn, error) {
conn, err := c.underlay.DialConn(address, tunnel)
conn, err := c.underlay.DialConn(address, &Tunnel{})
if err != nil {
return nil, err
}
+1 -1
View File
@@ -19,7 +19,7 @@ type Server struct {
}
func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {
conn, err := s.underlay.AcceptConn(overlay)
conn, err := s.underlay.AcceptConn(&Tunnel{})
if err != nil {
return nil, common.NewError("shadowsocks failed to accept connection from underlying tunnel")
}
+1 -3
View File
@@ -3,9 +3,7 @@ package socks
import "github.com/p4gefau1t/trojan-go/config"
type Config struct {
LocalHost string `json:"local_addr" yaml:"local-addr"`
LocalPort int `json:"local_port" yaml:"local-port"`
UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"`
UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"`
}
func init() {
+16 -21
View File
@@ -26,13 +26,13 @@ const (
// Server is a socks5 server
type Server struct {
connChan chan tunnel.Conn
packetChan chan tunnel.PacketConn
tcpListener net.Listener
localHost string
timeout time.Duration
ctx context.Context
cancel context.CancelFunc
connChan chan tunnel.Conn
packetChan chan tunnel.PacketConn
underlay tunnel.Server
localHost string
timeout time.Duration
ctx context.Context
cancel context.CancelFunc
}
func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {
@@ -55,7 +55,7 @@ func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
func (s *Server) Close() error {
s.cancel()
return s.tcpListener.Close()
return s.underlay.Close()
}
func (s *Server) handshake(conn net.Conn) (*Conn, error) {
@@ -110,7 +110,7 @@ func (s *Server) associate(conn net.Conn, addr *tunnel.Address) error {
func (s *Server) acceptLoop() {
for {
conn, err := s.tcpListener.Accept()
conn, err := s.underlay.AcceptConn(&Tunnel{})
if err != nil {
log.Error(common.NewError("socks5 accept err").Base(err))
return
@@ -134,6 +134,7 @@ func (s *Server) acceptLoop() {
case Associate:
defer newConn.Close()
port := common.PickPort("udp", s.localHost)
// TODO use underlying server
associateAddr := tunnel.NewAddressFromHostPort("udp", s.localHost, port)
l, err := net.ListenPacket("udp", associateAddr.String())
if err != nil {
@@ -160,22 +161,16 @@ func (s *Server) acceptLoop() {
// NewServer create a socks server
func NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) {
cfg := config.FromContext(ctx, Name).(*Config)
listenAddr := tunnel.NewAddressFromHostPort("tcp", cfg.LocalHost, cfg.LocalPort)
l, err := net.Listen("tcp", listenAddr.String())
if err != nil {
return nil, common.NewError("socks5 failed to listen").Base(err)
}
ctx, cancel := context.WithCancel(ctx)
server := &Server{
tcpListener: l,
ctx: ctx,
cancel: cancel,
connChan: make(chan tunnel.Conn, 32),
packetChan: make(chan tunnel.PacketConn, 32),
timeout: time.Duration(cfg.UDPTimeout) * time.Second,
underlay: underlay,
ctx: ctx,
cancel: cancel,
connChan: make(chan tunnel.Conn, 32),
packetChan: make(chan tunnel.PacketConn, 32),
timeout: time.Duration(cfg.UDPTimeout) * time.Second,
}
go server.acceptLoop()
log.Info("socks5 server is listening on tcp:", l.Addr().String())
log.Debug("socks server created")
return server, nil
}
+8 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/p4gefau1t/trojan-go/config"
"github.com/p4gefau1t/trojan-go/test/util"
"github.com/p4gefau1t/trojan-go/tunnel"
"github.com/p4gefau1t/trojan-go/tunnel/transport"
"golang.org/x/net/proxy"
"net"
"sync"
@@ -16,12 +17,17 @@ import (
func TestSocks(t *testing.T) {
port := common.PickPort("tcp", "127.0.0.1")
ctx := config.WithConfig(context.Background(), Name, &Config{
ctx := config.WithConfig(context.Background(), transport.Name, &transport.Config{
LocalHost: "127.0.0.1",
LocalPort: port,
})
ctx = config.WithConfig(ctx, Name, &Config{
UDPTimeout: 30,
})
tcpServer, err := transport.NewServer(ctx, nil)
common.Must(err)
addr := tunnel.NewAddressFromHostPort("tcp", "127.0.0.1", port)
s, err := NewServer(ctx, nil)
s, err := NewServer(ctx, tcpServer)
common.Must(err)
socksClient, err := proxy.SOCKS5("tcp", addr.String(), nil, proxy.Direct)
common.Must(err)
+1 -1
View File
@@ -80,7 +80,7 @@ func (s *Server) acceptLoop() {
func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {
// TODO fix import cycle
if overlay != nil && overlay.Name() == "WEBSOCKET" {
if overlay != nil && (overlay.Name() == "WEBSOCKET" || overlay.Name() == "HTTP") {
s.nextHTTP = true
select {
case conn := <-s.wsChan: