Skip to content

Commit

Permalink
Merge pull request #688 from apernet/wip-masq-tcp
Browse files Browse the repository at this point in the history
feat: HTTP/HTTPS masq servers
  • Loading branch information
tobyxdd authored Sep 16, 2023
2 parents 0084150 + 056c46f commit 6425098
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 6 deletions.
74 changes: 68 additions & 6 deletions app/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"

Expand All @@ -19,6 +20,7 @@ import (
"github.com/apernet/hysteria/app/internal/utils"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/extras/auth"
"github.com/apernet/hysteria/extras/masq"
"github.com/apernet/hysteria/extras/obfs"
"github.com/apernet/hysteria/extras/outbounds"
"github.com/apernet/hysteria/extras/trafficlogger"
Expand Down Expand Up @@ -177,9 +179,12 @@ type serverConfigMasqueradeProxy struct {
}

type serverConfigMasquerade struct {
Type string `mapstructure:"type"`
File serverConfigMasqueradeFile `mapstructure:"file"`
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
Type string `mapstructure:"type"`
File serverConfigMasqueradeFile `mapstructure:"file"`
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
ListenHTTP string `mapstructure:"listenHTTP"`
ListenHTTPS string `mapstructure:"listenHTTPS"`
ForceHTTPS bool `mapstructure:"forceHTTPS"`
}

func (c *serverConfig) fillConn(hyConfig *server.Config) error {
Expand Down Expand Up @@ -524,6 +529,8 @@ func (c *serverConfig) fillTrafficLogger(hyConfig *server.Config) error {
return nil
}

// fillMasqHandler must be called after fillConn, as we may need to extract the QUIC
// port number from Conn for MasqTCPServer.
func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
var handler http.Handler
switch strings.ToLower(c.Masquerade.Type) {
Expand Down Expand Up @@ -559,7 +566,24 @@ func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
default:
return configError{Field: "masquerade.type", Err: errors.New("unsupported masquerade type")}
}
hyConfig.MasqHandler = &masqHandlerLogWrapper{H: handler}
hyConfig.MasqHandler = &masqHandlerLogWrapper{H: handler, QUIC: true}

if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
return configError{Field: "masquerade.listenHTTPS", Err: errors.New("having only HTTP server without HTTPS is not supported")}
}
s := masq.MasqTCPServer{
QUICPort: extractPortFromAddr(hyConfig.Conn.LocalAddr().String()),
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
TLSConfig: &tls.Config{
Certificates: hyConfig.TLSConfig.Certificates,
GetCertificate: hyConfig.TLSConfig.GetCertificate,
},
ForceHTTPS: c.Masquerade.ForceHTTPS,
}
go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS)
}
return nil
}

Expand Down Expand Up @@ -626,6 +650,26 @@ func runTrafficStatsServer(listen string, handler http.Handler) {
}
}

func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string) {
errChan := make(chan error, 2)
if httpAddr != "" {
go func() {
logger.Info("masquerade HTTP server up and running", zap.String("listen", httpAddr))
errChan <- s.ListenAndServeHTTP(httpAddr)
}()
}
if httpsAddr != "" {
go func() {
logger.Info("masquerade HTTPS server up and running", zap.String("listen", httpsAddr))
errChan <- s.ListenAndServeHTTPS(httpsAddr)
}()
}
err := <-errChan
if err != nil {
logger.Fatal("failed to serve masquerade HTTP(S)", zap.Error(err))
}
}

func geoipDownloadFunc(filename, url string) {
logger.Info("downloading GeoIP database", zap.String("filename", filename), zap.String("url", url))
}
Expand Down Expand Up @@ -671,10 +715,28 @@ func (l *serverLogger) UDPError(addr net.Addr, id string, sessionID uint32, err
}

type masqHandlerLogWrapper struct {
H http.Handler
H http.Handler
QUIC bool
}

func (m *masqHandlerLogWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logger.Debug("masquerade request", zap.String("addr", r.RemoteAddr), zap.String("method", r.Method), zap.String("host", r.Host), zap.String("url", r.URL.String()))
logger.Debug("masquerade request",
zap.String("addr", r.RemoteAddr),
zap.String("method", r.Method),
zap.String("host", r.Host),
zap.String("url", r.URL.String()),
zap.Bool("quic", m.QUIC))
m.H.ServeHTTP(w, r)
}

func extractPortFromAddr(addr string) int {
_, portStr, err := net.SplitHostPort(addr)
if err != nil {
return 0
}
port, err := strconv.Atoi(portStr)
if err != nil {
return 0
}
return port
}
3 changes: 3 additions & 0 deletions app/cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func TestServerConfig(t *testing.T) {
URL: "https://some.site.net",
RewriteHost: true,
},
ListenHTTP: ":80",
ListenHTTPS: ":443",
ForceHTTPS: true,
},
})
}
3 changes: 3 additions & 0 deletions app/cmd/server_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ masquerade:
proxy:
url: https://some.site.net
rewriteHost: true
listenHTTP: :80
listenHTTPS: :443
forceHTTPS: true
62 changes: 62 additions & 0 deletions extras/masq/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package masq

import (
"crypto/tls"
"fmt"
"net/http"
)

// MasqTCPServer covers the TCP parts of a standard web server (TCP based HTTP/HTTPS).
// We provide this as an option for masquerading, as some may consider a server
// "suspicious" if it only serves the QUIC protocol and not standard HTTP/HTTPS.
type MasqTCPServer struct {
QUICPort int
HTTPSPort int
Handler http.Handler
TLSConfig *tls.Config
ForceHTTPS bool // Always 301 redirect from HTTP to HTTPS
}

func (s *MasqTCPServer) ListenAndServeHTTP(addr string) error {
return http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if s.ForceHTTPS {
if s.HTTPSPort == 0 || s.HTTPSPort == 443 {
// Omit port if it's the default
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
} else {
http.Redirect(w, r, fmt.Sprintf("https://%s:%d%s", r.Host, s.HTTPSPort, r.RequestURI), http.StatusMovedPermanently)
}
return
}
s.Handler.ServeHTTP(&altSvcHijackResponseWriter{
Port: s.QUICPort,
ResponseWriter: w,
}, r)
}))
}

func (s *MasqTCPServer) ListenAndServeHTTPS(addr string) error {
server := &http.Server{
Addr: addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.Handler.ServeHTTP(&altSvcHijackResponseWriter{
Port: s.QUICPort,
ResponseWriter: w,
}, r)
}),
TLSConfig: s.TLSConfig,
}
return server.ListenAndServeTLS("", "")
}

// altSvcHijackResponseWriter makes sure that the Alt-Svc's port
// is always set with our own value, no matter what the handler sets.
type altSvcHijackResponseWriter struct {
Port int
http.ResponseWriter
}

func (w *altSvcHijackResponseWriter) WriteHeader(statusCode int) {
w.Header().Set("Alt-Svc", fmt.Sprintf(`h3=":%d"; ma=2592000`, w.Port))
w.ResponseWriter.WriteHeader(statusCode)
}

0 comments on commit 6425098

Please sign in to comment.