411 lines
10 KiB
Go
411 lines
10 KiB
Go
package gost
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"errors"
|
|
"github.com/ginuerzh/pht"
|
|
"github.com/golang/glog"
|
|
"golang.org/x/net/http2"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"time"
|
|
)
|
|
|
|
type HttpServer struct {
|
|
conn net.Conn
|
|
Base *ProxyServer
|
|
}
|
|
|
|
func NewHttpServer(conn net.Conn, base *ProxyServer) *HttpServer {
|
|
return &HttpServer{
|
|
conn: conn,
|
|
Base: base,
|
|
}
|
|
}
|
|
|
|
// Default HTTP server handler
|
|
func (s *HttpServer) HandleRequest(req *http.Request) {
|
|
glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, s.conn.RemoteAddr(), req.Host, req.Proto)
|
|
|
|
if glog.V(LDEBUG) {
|
|
dump, _ := httputil.DumpRequest(req, false)
|
|
glog.Infoln(string(dump))
|
|
}
|
|
|
|
if req.Method == "PRI" && req.ProtoMajor == 2 {
|
|
glog.V(LWARNING).Infof("[http] %s <- %s : Not an HTTP2 server", s.conn.RemoteAddr(), req.Host)
|
|
resp := "HTTP/1.1 400 Bad Request\r\n" +
|
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
|
|
s.conn.Write([]byte(resp))
|
|
return
|
|
}
|
|
|
|
valid := false
|
|
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
|
for _, user := range s.Base.Node.Users {
|
|
username := user.Username()
|
|
password, _ := user.Password()
|
|
if (u == username && p == password) ||
|
|
(u == username && password == "") ||
|
|
(username == "" && p == password) {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(s.Base.Node.Users) > 0 && !valid {
|
|
glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", s.conn.RemoteAddr(), req.Host)
|
|
resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
|
|
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
|
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n"
|
|
s.conn.Write([]byte(resp))
|
|
return
|
|
}
|
|
|
|
req.Header.Del("Proxy-Authorization")
|
|
|
|
// forward http request
|
|
lastNode := s.Base.Chain.lastNode
|
|
if lastNode != nil && lastNode.Transport == "" && (lastNode.Protocol == "http" || lastNode.Protocol == "") {
|
|
s.forwardRequest(req)
|
|
return
|
|
}
|
|
|
|
c, err := s.Base.Chain.Dial(req.Host)
|
|
if err != nil {
|
|
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err)
|
|
|
|
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
|
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), req.Host, string(b))
|
|
s.conn.Write(b)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
if req.Method == http.MethodConnect {
|
|
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
|
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), req.Host, string(b))
|
|
s.conn.Write(b)
|
|
} else {
|
|
req.Header.Del("Proxy-Connection")
|
|
req.Header.Set("Connection", "Keep-Alive")
|
|
|
|
if err = req.Write(c); err != nil {
|
|
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
glog.V(LINFO).Infof("[http] %s <-> %s", s.conn.RemoteAddr(), req.Host)
|
|
s.Base.transport(s.conn, c)
|
|
glog.V(LINFO).Infof("[http] %s >-< %s", s.conn.RemoteAddr(), req.Host)
|
|
}
|
|
|
|
func (s *HttpServer) forwardRequest(req *http.Request) {
|
|
last := s.Base.Chain.lastNode
|
|
if last == nil {
|
|
return
|
|
}
|
|
cc, err := s.Base.Chain.GetConn()
|
|
if err != nil {
|
|
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), last.Addr, err)
|
|
|
|
b := []byte("HTTP/1.1 503 Service unavailable\r\n" +
|
|
"Proxy-Agent: gost/" + Version + "\r\n\r\n")
|
|
glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", s.conn.RemoteAddr(), last.Addr, string(b))
|
|
s.conn.Write(b)
|
|
return
|
|
}
|
|
defer cc.Close()
|
|
|
|
if len(last.Users) > 0 {
|
|
user := last.Users[0]
|
|
s := user.String()
|
|
if _, set := user.Password(); !set {
|
|
s += ":"
|
|
}
|
|
req.Header.Set("Proxy-Authorization",
|
|
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
|
}
|
|
|
|
cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
|
|
if err = req.WriteProxy(cc); err != nil {
|
|
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err)
|
|
return
|
|
}
|
|
cc.SetWriteDeadline(time.Time{})
|
|
|
|
glog.V(LINFO).Infof("[http] %s <-> %s", s.conn.RemoteAddr(), req.Host)
|
|
s.Base.transport(s.conn, cc)
|
|
glog.V(LINFO).Infof("[http] %s >-< %s", s.conn.RemoteAddr(), req.Host)
|
|
return
|
|
}
|
|
|
|
type Http2Server struct {
|
|
Base *ProxyServer
|
|
Handler http.Handler
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
func NewHttp2Server(base *ProxyServer) *Http2Server {
|
|
return &Http2Server{Base: base}
|
|
}
|
|
|
|
func (s *Http2Server) ListenAndServeTLS(config *tls.Config) error {
|
|
srv := http.Server{
|
|
Addr: s.Base.Node.Addr,
|
|
Handler: s.Handler,
|
|
TLSConfig: config,
|
|
}
|
|
if srv.Handler == nil {
|
|
srv.Handler = http.HandlerFunc(s.HandleRequest)
|
|
}
|
|
http2.ConfigureServer(&srv, nil)
|
|
return srv.ListenAndServeTLS("", "")
|
|
}
|
|
|
|
// Default HTTP2 server handler
|
|
func (s *Http2Server) HandleRequest(w http.ResponseWriter, req *http.Request) {
|
|
target := req.Header.Get("Gost-Target")
|
|
if target == "" {
|
|
target = req.Host
|
|
}
|
|
glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto)
|
|
if glog.V(LDEBUG) {
|
|
dump, _ := httputil.DumpRequest(req, false)
|
|
glog.Infoln(string(dump))
|
|
}
|
|
|
|
w.Header().Set("Proxy-Agent", "gost/"+Version)
|
|
|
|
// HTTP2 as transport
|
|
if req.Header.Get("Proxy-Switch") == "gost" {
|
|
conn, err := s.Upgrade(w, req)
|
|
if err != nil {
|
|
glog.V(LINFO).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
|
|
return
|
|
}
|
|
glog.V(LINFO).Infof("[http2] %s - %s : switch to HTTP2 transport mode OK", req.RemoteAddr, target)
|
|
s.Base.handleConn(conn)
|
|
return
|
|
}
|
|
|
|
valid := false
|
|
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
|
for _, user := range s.Base.Node.Users {
|
|
username := user.Username()
|
|
password, _ := user.Password()
|
|
if (u == username && p == password) ||
|
|
(u == username && password == "") ||
|
|
(username == "" && p == password) {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if len(s.Base.Node.Users) > 0 && !valid {
|
|
glog.V(LWARNING).Infof("[http2] %s <- %s : proxy authentication required", req.RemoteAddr, target)
|
|
w.WriteHeader(http.StatusProxyAuthRequired)
|
|
return
|
|
}
|
|
|
|
req.Header.Del("Proxy-Authorization")
|
|
req.Header.Del("Proxy-Connection")
|
|
|
|
c, err := s.Base.Chain.Dial(target)
|
|
if err != nil {
|
|
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target)
|
|
|
|
if req.Method == http.MethodConnect {
|
|
w.WriteHeader(http.StatusOK)
|
|
if fw, ok := w.(http.Flusher); ok {
|
|
fw.Flush()
|
|
}
|
|
|
|
// compatible with HTTP1.x
|
|
if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 {
|
|
// we take over the underly connection
|
|
conn, _, err := hj.Hijack()
|
|
if err != nil {
|
|
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
glog.V(LINFO).Infof("[http2] %s -> %s : downgrade to HTTP/1.1", req.RemoteAddr, target)
|
|
s.Base.transport(conn, c)
|
|
return
|
|
}
|
|
|
|
errc := make(chan error, 2)
|
|
go func() {
|
|
_, err := io.Copy(c, req.Body)
|
|
errc <- err
|
|
}()
|
|
go func() {
|
|
_, err := io.Copy(flushWriter{w}, c)
|
|
errc <- err
|
|
}()
|
|
|
|
select {
|
|
case <-errc:
|
|
// glog.V(LWARNING).Infoln("exit", err)
|
|
}
|
|
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target)
|
|
return
|
|
}
|
|
|
|
req.Header.Set("Connection", "Keep-Alive")
|
|
if err = req.Write(c); err != nil {
|
|
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
|
|
return
|
|
}
|
|
|
|
resp, err := http.ReadResponse(bufio.NewReader(c), req)
|
|
if err != nil {
|
|
glog.V(LWARNING).Infoln("[http2] %s -> %s : %s", req.RemoteAddr, target, err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
for k, v := range resp.Header {
|
|
for _, vv := range v {
|
|
w.Header().Add(k, vv)
|
|
}
|
|
}
|
|
w.WriteHeader(resp.StatusCode)
|
|
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil {
|
|
glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err)
|
|
}
|
|
glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target)
|
|
}
|
|
|
|
// Upgrade upgrade an HTTP2 request to a bidirectional connection that preparing for tunneling other protocol, just like a websocket connection.
|
|
func (s *Http2Server) Upgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
|
|
if r.Method != http.MethodConnect {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return nil, errors.New("Method not allowed")
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if fw, ok := w.(http.Flusher); ok {
|
|
fw.Flush()
|
|
}
|
|
|
|
conn := &http2Conn{r: r.Body, w: flushWriter{w}}
|
|
conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", r.RemoteAddr)
|
|
conn.localAddr, _ = net.ResolveTCPAddr("tcp", r.Host)
|
|
return conn, nil
|
|
}
|
|
|
|
// HTTP2 client connection, wrapped up just like a net.Conn
|
|
type http2Conn struct {
|
|
r io.Reader
|
|
w io.Writer
|
|
remoteAddr net.Addr
|
|
localAddr net.Addr
|
|
}
|
|
|
|
func (c *http2Conn) Read(b []byte) (n int, err error) {
|
|
return c.r.Read(b)
|
|
}
|
|
|
|
func (c *http2Conn) Write(b []byte) (n int, err error) {
|
|
return c.w.Write(b)
|
|
}
|
|
|
|
func (c *http2Conn) Close() (err error) {
|
|
if rc, ok := c.r.(io.Closer); ok {
|
|
err = rc.Close()
|
|
}
|
|
if w, ok := c.w.(io.Closer); ok {
|
|
err = w.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *http2Conn) LocalAddr() net.Addr {
|
|
return c.localAddr
|
|
}
|
|
|
|
func (c *http2Conn) RemoteAddr() net.Addr {
|
|
return c.remoteAddr
|
|
}
|
|
|
|
func (c *http2Conn) SetDeadline(t time.Time) error {
|
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
}
|
|
|
|
func (c *http2Conn) SetReadDeadline(t time.Time) error {
|
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
}
|
|
|
|
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
|
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
}
|
|
|
|
type flushWriter struct {
|
|
w io.Writer
|
|
}
|
|
|
|
func (fw flushWriter) Write(p []byte) (n int, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if s, ok := r.(string); ok {
|
|
err = errors.New(s)
|
|
return
|
|
}
|
|
err = r.(error)
|
|
}
|
|
}()
|
|
|
|
n, err = fw.w.Write(p)
|
|
if err != nil {
|
|
// glog.V(LWARNING).Infoln("flush writer:", err)
|
|
return
|
|
}
|
|
if f, ok := fw.w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
return
|
|
}
|
|
|
|
type PureHttpServer struct {
|
|
Base *ProxyServer
|
|
Handler func(net.Conn)
|
|
}
|
|
|
|
func NewPureHttpServer(base *ProxyServer) *PureHttpServer {
|
|
return &PureHttpServer{
|
|
Base: base,
|
|
}
|
|
}
|
|
|
|
func (s *PureHttpServer) ListenAndServe() error {
|
|
server := pht.Server{
|
|
Addr: s.Base.Node.Addr,
|
|
Key: s.Base.Node.Get("key"),
|
|
}
|
|
if server.Handler == nil {
|
|
server.Handler = s.handleConn
|
|
}
|
|
return server.ListenAndServe()
|
|
}
|
|
|
|
func (s *PureHttpServer) handleConn(conn net.Conn) {
|
|
glog.V(LINFO).Infof("[pht] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
|
s.Base.handleConn(conn)
|
|
}
|