mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2024-04-21 12:21:34 +00:00
199 lines
4.6 KiB
Go
199 lines
4.6 KiB
Go
package http
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/p4gefau1t/trojan-go/common"
|
|
"github.com/p4gefau1t/trojan-go/log"
|
|
"github.com/p4gefau1t/trojan-go/tunnel"
|
|
)
|
|
|
|
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")
|
|
}
|
|
for range 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 {
|
|
reqReader, reqWriter := io.Pipe()
|
|
respReader, respWriter := io.Pipe()
|
|
var addr *tunnel.Address
|
|
if addr, err = tunnel.NewAddressFromAddr("tcp", req.Host); err != nil {
|
|
addr = tunnel.NewAddressFromHostPort("tcp", req.Host, 80)
|
|
}
|
|
log.Debug("http dest", addr)
|
|
|
|
ctx, cancel := context.WithCancel(s.ctx)
|
|
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
|
|
}
|