|
|
package server |
|
|
|
|
|
import ( |
|
|
"crypto/tls" |
|
|
"net" |
|
|
"net/http" |
|
|
"sync/atomic" |
|
|
"time" |
|
|
|
|
|
"github.com/apernet/hysteria/core/v2/errors" |
|
|
"github.com/apernet/hysteria/core/v2/internal/pmtud" |
|
|
"github.com/apernet/hysteria/core/v2/internal/utils" |
|
|
"github.com/apernet/quic-go" |
|
|
) |
|
|
|
|
|
const ( |
|
|
defaultStreamReceiveWindow = 8388608 |
|
|
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 |
|
|
defaultMaxIdleTimeout = 30 * time.Second |
|
|
defaultMaxIncomingStreams = 1024 |
|
|
defaultUDPIdleTimeout = 60 * time.Second |
|
|
) |
|
|
|
|
|
type Config struct { |
|
|
TLSConfig TLSConfig |
|
|
QUICConfig QUICConfig |
|
|
Conn net.PacketConn |
|
|
RequestHook RequestHook |
|
|
Outbound Outbound |
|
|
BandwidthConfig BandwidthConfig |
|
|
IgnoreClientBandwidth bool |
|
|
DisableUDP bool |
|
|
UDPIdleTimeout time.Duration |
|
|
Authenticator Authenticator |
|
|
EventLogger EventLogger |
|
|
TrafficLogger TrafficLogger |
|
|
MasqHandler http.Handler |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *Config) fill() error { |
|
|
if len(c.TLSConfig.Certificates) == 0 && c.TLSConfig.GetCertificate == nil { |
|
|
return errors.ConfigError{Field: "TLSConfig", Reason: "must set at least one of Certificates or GetCertificate"} |
|
|
} |
|
|
if c.QUICConfig.InitialStreamReceiveWindow == 0 { |
|
|
c.QUICConfig.InitialStreamReceiveWindow = defaultStreamReceiveWindow |
|
|
} else if c.QUICConfig.InitialStreamReceiveWindow < 16384 { |
|
|
return errors.ConfigError{Field: "QUICConfig.InitialStreamReceiveWindow", Reason: "must be at least 16384"} |
|
|
} |
|
|
if c.QUICConfig.MaxStreamReceiveWindow == 0 { |
|
|
c.QUICConfig.MaxStreamReceiveWindow = defaultStreamReceiveWindow |
|
|
} else if c.QUICConfig.MaxStreamReceiveWindow < 16384 { |
|
|
return errors.ConfigError{Field: "QUICConfig.MaxStreamReceiveWindow", Reason: "must be at least 16384"} |
|
|
} |
|
|
if c.QUICConfig.InitialConnectionReceiveWindow == 0 { |
|
|
c.QUICConfig.InitialConnectionReceiveWindow = defaultConnReceiveWindow |
|
|
} else if c.QUICConfig.InitialConnectionReceiveWindow < 16384 { |
|
|
return errors.ConfigError{Field: "QUICConfig.InitialConnectionReceiveWindow", Reason: "must be at least 16384"} |
|
|
} |
|
|
if c.QUICConfig.MaxConnectionReceiveWindow == 0 { |
|
|
c.QUICConfig.MaxConnectionReceiveWindow = defaultConnReceiveWindow |
|
|
} else if c.QUICConfig.MaxConnectionReceiveWindow < 16384 { |
|
|
return errors.ConfigError{Field: "QUICConfig.MaxConnectionReceiveWindow", Reason: "must be at least 16384"} |
|
|
} |
|
|
if c.QUICConfig.MaxIdleTimeout == 0 { |
|
|
c.QUICConfig.MaxIdleTimeout = defaultMaxIdleTimeout |
|
|
} else if c.QUICConfig.MaxIdleTimeout < 4*time.Second || c.QUICConfig.MaxIdleTimeout > 120*time.Second { |
|
|
return errors.ConfigError{Field: "QUICConfig.MaxIdleTimeout", Reason: "must be between 4s and 120s"} |
|
|
} |
|
|
if c.QUICConfig.MaxIncomingStreams == 0 { |
|
|
c.QUICConfig.MaxIncomingStreams = defaultMaxIncomingStreams |
|
|
} else if c.QUICConfig.MaxIncomingStreams < 8 { |
|
|
return errors.ConfigError{Field: "QUICConfig.MaxIncomingStreams", Reason: "must be at least 8"} |
|
|
} |
|
|
c.QUICConfig.DisablePathMTUDiscovery = c.QUICConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery |
|
|
if c.Conn == nil { |
|
|
return errors.ConfigError{Field: "Conn", Reason: "must be set"} |
|
|
} |
|
|
if c.Outbound == nil { |
|
|
c.Outbound = &defaultOutbound{} |
|
|
} |
|
|
if c.BandwidthConfig.MaxTx != 0 && c.BandwidthConfig.MaxTx < 65536 { |
|
|
return errors.ConfigError{Field: "BandwidthConfig.MaxTx", Reason: "must be at least 65536"} |
|
|
} |
|
|
if c.BandwidthConfig.MaxRx != 0 && c.BandwidthConfig.MaxRx < 65536 { |
|
|
return errors.ConfigError{Field: "BandwidthConfig.MaxRx", Reason: "must be at least 65536"} |
|
|
} |
|
|
if c.UDPIdleTimeout == 0 { |
|
|
c.UDPIdleTimeout = defaultUDPIdleTimeout |
|
|
} else if c.UDPIdleTimeout < 2*time.Second || c.UDPIdleTimeout > 600*time.Second { |
|
|
return errors.ConfigError{Field: "UDPIdleTimeout", Reason: "must be between 2s and 600s"} |
|
|
} |
|
|
if c.Authenticator == nil { |
|
|
return errors.ConfigError{Field: "Authenticator", Reason: "must be set"} |
|
|
} |
|
|
return nil |
|
|
} |
|
|
|
|
|
|
|
|
type TLSConfig struct { |
|
|
Certificates []tls.Certificate |
|
|
GetCertificate func(info *tls.ClientHelloInfo) (*tls.Certificate, error) |
|
|
} |
|
|
|
|
|
|
|
|
type QUICConfig struct { |
|
|
InitialStreamReceiveWindow uint64 |
|
|
MaxStreamReceiveWindow uint64 |
|
|
InitialConnectionReceiveWindow uint64 |
|
|
MaxConnectionReceiveWindow uint64 |
|
|
MaxIdleTimeout time.Duration |
|
|
MaxIncomingStreams int64 |
|
|
DisablePathMTUDiscovery bool |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type RequestHook interface { |
|
|
Check(isUDP bool, reqAddr string) bool |
|
|
TCP(stream quic.Stream, reqAddr *string) ([]byte, error) |
|
|
UDP(data []byte, reqAddr *string) error |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type Outbound interface { |
|
|
TCP(reqAddr string) (net.Conn, error) |
|
|
UDP(reqAddr string) (UDPConn, error) |
|
|
} |
|
|
|
|
|
|
|
|
type UDPConn interface { |
|
|
ReadFrom(b []byte) (int, string, error) |
|
|
WriteTo(b []byte, addr string) (int, error) |
|
|
Close() error |
|
|
} |
|
|
|
|
|
type defaultOutbound struct{} |
|
|
|
|
|
var defaultOutboundDialer = net.Dialer{ |
|
|
Timeout: 10 * time.Second, |
|
|
} |
|
|
|
|
|
func (o *defaultOutbound) TCP(reqAddr string) (net.Conn, error) { |
|
|
return defaultOutboundDialer.Dial("tcp", reqAddr) |
|
|
} |
|
|
|
|
|
func (o *defaultOutbound) UDP(reqAddr string) (UDPConn, error) { |
|
|
conn, err := net.ListenUDP("udp", nil) |
|
|
if err != nil { |
|
|
return nil, err |
|
|
} |
|
|
return &defaultUDPConn{conn}, nil |
|
|
} |
|
|
|
|
|
type defaultUDPConn struct { |
|
|
*net.UDPConn |
|
|
} |
|
|
|
|
|
func (c *defaultUDPConn) ReadFrom(b []byte) (int, string, error) { |
|
|
n, addr, err := c.UDPConn.ReadFrom(b) |
|
|
if addr != nil { |
|
|
return n, addr.String(), err |
|
|
} else { |
|
|
return n, "", err |
|
|
} |
|
|
} |
|
|
|
|
|
func (c *defaultUDPConn) WriteTo(b []byte, addr string) (int, error) { |
|
|
uAddr, err := net.ResolveUDPAddr("udp", addr) |
|
|
if err != nil { |
|
|
return 0, err |
|
|
} |
|
|
return c.UDPConn.WriteTo(b, uAddr) |
|
|
} |
|
|
|
|
|
|
|
|
type BandwidthConfig struct { |
|
|
MaxTx uint64 |
|
|
MaxRx uint64 |
|
|
} |
|
|
|
|
|
|
|
|
type Authenticator interface { |
|
|
Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) |
|
|
} |
|
|
|
|
|
|
|
|
type EventLogger interface { |
|
|
Connect(addr net.Addr, id string, tx uint64) |
|
|
Disconnect(addr net.Addr, id string, err error) |
|
|
TCPRequest(addr net.Addr, id, reqAddr string) |
|
|
TCPError(addr net.Addr, id, reqAddr string, err error) |
|
|
UDPRequest(addr net.Addr, id string, sessionID uint32, reqAddr string) |
|
|
UDPError(addr net.Addr, id string, sessionID uint32, err error) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type TrafficLogger interface { |
|
|
LogTraffic(id string, tx, rx uint64) (ok bool) |
|
|
LogOnlineState(id string, online bool) |
|
|
TraceStream(stream quic.Stream, stats *StreamStats) |
|
|
UntraceStream(stream quic.Stream) |
|
|
} |
|
|
|
|
|
type StreamState int |
|
|
|
|
|
const ( |
|
|
|
|
|
|
|
|
StreamStateInitial StreamState = iota |
|
|
|
|
|
|
|
|
|
|
|
StreamStateHooking |
|
|
|
|
|
|
|
|
StreamStateConnecting |
|
|
|
|
|
|
|
|
StreamStateEstablished |
|
|
|
|
|
|
|
|
StreamStateClosed |
|
|
) |
|
|
|
|
|
func (s StreamState) String() string { |
|
|
switch s { |
|
|
case StreamStateInitial: |
|
|
return "init" |
|
|
case StreamStateHooking: |
|
|
return "hook" |
|
|
case StreamStateConnecting: |
|
|
return "connect" |
|
|
case StreamStateEstablished: |
|
|
return "estab" |
|
|
case StreamStateClosed: |
|
|
return "closed" |
|
|
default: |
|
|
return "unknown" |
|
|
} |
|
|
} |
|
|
|
|
|
type StreamStats struct { |
|
|
State utils.Atomic[StreamState] |
|
|
|
|
|
AuthID string |
|
|
ConnID uint32 |
|
|
InitialTime time.Time |
|
|
|
|
|
ReqAddr utils.Atomic[string] |
|
|
HookedReqAddr utils.Atomic[string] |
|
|
|
|
|
Tx atomic.Uint64 |
|
|
Rx atomic.Uint64 |
|
|
|
|
|
LastActiveTime utils.Atomic[time.Time] |
|
|
} |
|
|
|
|
|
func (s *StreamStats) setHookedReqAddr(addr string) { |
|
|
if addr != s.ReqAddr.Load() { |
|
|
s.HookedReqAddr.Store(addr) |
|
|
} |
|
|
} |
|
|
|