mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-06-08 13:29:56 +00:00
feat: HTTP/HTTPS masq servers
This commit is contained in:
parent
c73570f582
commit
056c46f4d0
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/apernet/hysteria/app/internal/utils"
|
"github.com/apernet/hysteria/app/internal/utils"
|
||||||
"github.com/apernet/hysteria/core/server"
|
"github.com/apernet/hysteria/core/server"
|
||||||
"github.com/apernet/hysteria/extras/auth"
|
"github.com/apernet/hysteria/extras/auth"
|
||||||
|
"github.com/apernet/hysteria/extras/masq"
|
||||||
"github.com/apernet/hysteria/extras/obfs"
|
"github.com/apernet/hysteria/extras/obfs"
|
||||||
"github.com/apernet/hysteria/extras/outbounds"
|
"github.com/apernet/hysteria/extras/outbounds"
|
||||||
"github.com/apernet/hysteria/extras/trafficlogger"
|
"github.com/apernet/hysteria/extras/trafficlogger"
|
||||||
@ -180,6 +182,9 @@ type serverConfigMasquerade struct {
|
|||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
File serverConfigMasqueradeFile `mapstructure:"file"`
|
File serverConfigMasqueradeFile `mapstructure:"file"`
|
||||||
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
|
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
|
||||||
|
ListenHTTP string `mapstructure:"listenHTTP"`
|
||||||
|
ListenHTTPS string `mapstructure:"listenHTTPS"`
|
||||||
|
ForceHTTPS bool `mapstructure:"forceHTTPS"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *serverConfig) fillConn(hyConfig *server.Config) error {
|
func (c *serverConfig) fillConn(hyConfig *server.Config) error {
|
||||||
@ -524,6 +529,8 @@ func (c *serverConfig) fillTrafficLogger(hyConfig *server.Config) error {
|
|||||||
return nil
|
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 {
|
func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
|
||||||
var handler http.Handler
|
var handler http.Handler
|
||||||
switch strings.ToLower(c.Masquerade.Type) {
|
switch strings.ToLower(c.Masquerade.Type) {
|
||||||
@ -559,7 +566,24 @@ func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
|
|||||||
default:
|
default:
|
||||||
return configError{Field: "masquerade.type", Err: errors.New("unsupported masquerade type")}
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
func geoipDownloadFunc(filename, url string) {
|
||||||
logger.Info("downloading GeoIP database", zap.String("filename", filename), zap.String("url", url))
|
logger.Info("downloading GeoIP database", zap.String("filename", filename), zap.String("url", url))
|
||||||
}
|
}
|
||||||
@ -672,9 +716,27 @@ func (l *serverLogger) UDPError(addr net.Addr, id string, sessionID uint32, err
|
|||||||
|
|
||||||
type masqHandlerLogWrapper struct {
|
type masqHandlerLogWrapper struct {
|
||||||
H http.Handler
|
H http.Handler
|
||||||
|
QUIC bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *masqHandlerLogWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
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)
|
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
|
||||||
|
}
|
||||||
|
@ -136,6 +136,9 @@ func TestServerConfig(t *testing.T) {
|
|||||||
URL: "https://some.site.net",
|
URL: "https://some.site.net",
|
||||||
RewriteHost: true,
|
RewriteHost: true,
|
||||||
},
|
},
|
||||||
|
ListenHTTP: ":80",
|
||||||
|
ListenHTTPS: ":443",
|
||||||
|
ForceHTTPS: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -102,3 +102,6 @@ masquerade:
|
|||||||
proxy:
|
proxy:
|
||||||
url: https://some.site.net
|
url: https://some.site.net
|
||||||
rewriteHost: true
|
rewriteHost: true
|
||||||
|
listenHTTP: :80
|
||||||
|
listenHTTPS: :443
|
||||||
|
forceHTTPS: true
|
||||||
|
62
extras/masq/server.go
Normal file
62
extras/masq/server.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user