github.com/ipfans/trojan-go@v0.11.0/tunnel/http/server.go (about)

     1  package http
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"strings"
    12  
    13  	"github.com/ipfans/trojan-go/common"
    14  	"github.com/ipfans/trojan-go/log"
    15  	"github.com/ipfans/trojan-go/tunnel"
    16  )
    17  
    18  type ConnectConn struct {
    19  	net.Conn
    20  	metadata *tunnel.Metadata
    21  }
    22  
    23  func (c *ConnectConn) Metadata() *tunnel.Metadata {
    24  	return c.metadata
    25  }
    26  
    27  type OtherConn struct {
    28  	net.Conn
    29  	metadata   *tunnel.Metadata // fixed
    30  	reqReader  *io.PipeReader
    31  	respWriter *io.PipeWriter
    32  	ctx        context.Context
    33  	cancel     context.CancelFunc
    34  }
    35  
    36  func (c *OtherConn) Metadata() *tunnel.Metadata {
    37  	return c.metadata
    38  }
    39  
    40  func (c *OtherConn) Read(p []byte) (int, error) {
    41  	n, err := c.reqReader.Read(p)
    42  	if err == io.EOF {
    43  		if n != 0 {
    44  			panic("non zero")
    45  		}
    46  		for range c.ctx.Done() {
    47  			return 0, common.NewError("http conn closed")
    48  		}
    49  	}
    50  	return n, err
    51  }
    52  
    53  func (c *OtherConn) Write(p []byte) (int, error) {
    54  	return c.respWriter.Write(p)
    55  }
    56  
    57  func (c *OtherConn) Close() error {
    58  	c.cancel()
    59  	c.reqReader.Close()
    60  	c.respWriter.Close()
    61  	return nil
    62  }
    63  
    64  type Server struct {
    65  	underlay tunnel.Server
    66  	connChan chan tunnel.Conn
    67  	ctx      context.Context
    68  	cancel   context.CancelFunc
    69  }
    70  
    71  func (s *Server) acceptLoop() {
    72  	for {
    73  		conn, err := s.underlay.AcceptConn(&Tunnel{})
    74  		if err != nil {
    75  			select {
    76  			case <-s.ctx.Done():
    77  				log.Error(common.NewError("http closed"))
    78  				return
    79  			default:
    80  				log.Error(common.NewError("http failed to accept connection").Base(err))
    81  				continue
    82  			}
    83  		}
    84  
    85  		go func(conn net.Conn) {
    86  			reqBufReader := bufio.NewReader(ioutil.NopCloser(conn))
    87  			req, err := http.ReadRequest(reqBufReader)
    88  			if err != nil {
    89  				log.Error(common.NewError("not a valid http request").Base(err))
    90  				return
    91  			}
    92  
    93  			if strings.ToUpper(req.Method) == "CONNECT" { // CONNECT
    94  				addr, err := tunnel.NewAddressFromAddr("tcp", req.Host)
    95  				if err != nil {
    96  					log.Error(common.NewError("invalid http dest address").Base(err))
    97  					conn.Close()
    98  					return
    99  				}
   100  				resp := fmt.Sprintf("HTTP/%d.%d 200 Connection established\r\n\r\n", req.ProtoMajor, req.ProtoMinor)
   101  				_, err = conn.Write([]byte(resp))
   102  				if err != nil {
   103  					log.Error("http failed to respond connect request")
   104  					conn.Close()
   105  					return
   106  				}
   107  				s.connChan <- &ConnectConn{
   108  					Conn: conn,
   109  					metadata: &tunnel.Metadata{
   110  						Address: addr,
   111  					},
   112  				}
   113  			} else { // GET, POST, PUT...
   114  				defer conn.Close()
   115  				for {
   116  					reqReader, reqWriter := io.Pipe()
   117  					respReader, respWriter := io.Pipe()
   118  					var addr *tunnel.Address
   119  					if addr, err = tunnel.NewAddressFromAddr("tcp", req.Host); err != nil {
   120  						addr = tunnel.NewAddressFromHostPort("tcp", req.Host, 80)
   121  					}
   122  					log.Debug("http dest", addr)
   123  
   124  					ctx, cancel := context.WithCancel(s.ctx)
   125  					newConn := &OtherConn{
   126  						Conn: conn,
   127  						metadata: &tunnel.Metadata{
   128  							Address: addr,
   129  						},
   130  						ctx:        ctx,
   131  						cancel:     cancel,
   132  						reqReader:  reqReader,
   133  						respWriter: respWriter,
   134  					}
   135  					s.connChan <- newConn // pass this http session connection to proxy.RelayConn
   136  
   137  					err = req.Write(reqWriter) // write request to the remote
   138  					if err != nil {
   139  						log.Error(common.NewError("http failed to write http request").Base(err))
   140  						return
   141  					}
   142  
   143  					respBufReader := bufio.NewReader(ioutil.NopCloser(respReader)) // read response from the remote
   144  					resp, err := http.ReadResponse(respBufReader, req)
   145  					if err != nil {
   146  						log.Error(common.NewError("http failed to read http response").Base(err))
   147  						return
   148  					}
   149  					err = resp.Write(conn) // send the response back to the local
   150  					if err != nil {
   151  						log.Error(common.NewError("http failed to write the response back").Base(err))
   152  						return
   153  					}
   154  					newConn.Close()
   155  					req.Body.Close()
   156  					resp.Body.Close()
   157  
   158  					req, err = http.ReadRequest(reqBufReader) // read the next http request from local
   159  					if err != nil {
   160  						log.Error(common.NewError("http failed to the read request from local").Base(err))
   161  						return
   162  					}
   163  				}
   164  			}
   165  		}(conn)
   166  	}
   167  }
   168  
   169  func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {
   170  	select {
   171  	case conn := <-s.connChan:
   172  		return conn, nil
   173  	case <-s.ctx.Done():
   174  		return nil, common.NewError("http server closed")
   175  	}
   176  }
   177  
   178  func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
   179  	<-s.ctx.Done()
   180  	return nil, common.NewError("http server closed")
   181  }
   182  
   183  func (s *Server) Close() error {
   184  	s.cancel()
   185  	return s.underlay.Close()
   186  }
   187  
   188  func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {
   189  	ctx, cancel := context.WithCancel(ctx)
   190  	server := &Server{
   191  		underlay: underlay,
   192  		connChan: make(chan tunnel.Conn, 32),
   193  		ctx:      ctx,
   194  		cancel:   cancel,
   195  	}
   196  	go server.acceptLoop()
   197  	return server, nil
   198  }