mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 04:30:08 +00:00
hy2内核增加自定义配置
This commit is contained in:
@@ -77,37 +77,49 @@ func (n *Hysteria2node) getTLSConfig(config *conf.Options) (*server.TLSConfig, e
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getQUICConfig(config *conf.Options) (*server.QUICConfig, error) {
|
||||
func (n *Hysteria2node) getQUICConfig(config *serverConfig) (*server.QUICConfig, error) {
|
||||
quic := &server.QUICConfig{}
|
||||
if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow == 0 {
|
||||
if config.QUIC.InitStreamReceiveWindow == 0 {
|
||||
quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow < 16384 {
|
||||
} else if config.QUIC.InitStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow == 0 {
|
||||
if config.QUIC.MaxStreamReceiveWindow == 0 {
|
||||
quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow < 16384 {
|
||||
} else if config.QUIC.MaxStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.MaxStreamReceiveWindow = config.QUIC.MaxStreamReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow == 0 {
|
||||
if config.QUIC.InitConnectionReceiveWindow == 0 {
|
||||
quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow < 16384 {
|
||||
} else if config.QUIC.InitConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow == 0 {
|
||||
if config.QUIC.MaxConnectionReceiveWindow == 0 {
|
||||
quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow < 16384 {
|
||||
} else if config.QUIC.MaxConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.MaxConnectionReceiveWindow = config.QUIC.MaxConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIdleTimeout == 0 {
|
||||
if config.QUIC.MaxIdleTimeout == 0 {
|
||||
quic.MaxIdleTimeout = defaultMaxIdleTimeout
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIdleTimeout < 4*time.Second || config.Hysteria2Options.QUICConfig.MaxIdleTimeout > 120*time.Second {
|
||||
} else if config.QUIC.MaxIdleTimeout < 4*time.Second || config.QUIC.MaxIdleTimeout > 120*time.Second {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s")
|
||||
} else {
|
||||
quic.MaxIdleTimeout = config.QUIC.MaxIdleTimeout
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIncomingStreams == 0 {
|
||||
if config.QUIC.MaxIncomingStreams == 0 {
|
||||
quic.MaxIncomingStreams = defaultMaxIncomingStreams
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIncomingStreams < 8 {
|
||||
} else if config.QUIC.MaxIncomingStreams < 8 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8")
|
||||
} else {
|
||||
quic.MaxIncomingStreams = config.QUIC.MaxIncomingStreams
|
||||
}
|
||||
// todo fix !linux && !windows && !darwin
|
||||
quic.DisablePathMTUDiscovery = false
|
||||
@@ -149,19 +161,24 @@ func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.Bandwid
|
||||
return band
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound, error) {
|
||||
func (n *Hysteria2node) getOutboundConfig(c *serverConfig) (server.Outbound, error) {
|
||||
// Resolver, ACL, actual outbound are all implemented through the Outbound interface.
|
||||
// Depending on the config, we build a chain like this:
|
||||
// Resolver(ACL(Outbounds...))
|
||||
|
||||
// Outbounds
|
||||
var obs []outbounds.OutboundEntry
|
||||
if len(config.Hysteria2Options.Outbounds) == 0 {
|
||||
if len(c.Outbounds) == 0 {
|
||||
// Guarantee we have at least one outbound
|
||||
obs = []outbounds.OutboundEntry{{
|
||||
Name: "default",
|
||||
Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto),
|
||||
}}
|
||||
} else {
|
||||
obs = make([]outbounds.OutboundEntry, len(config.Hysteria2Options.Outbounds))
|
||||
for i, entry := range config.Hysteria2Options.Outbounds {
|
||||
obs = make([]outbounds.OutboundEntry, len(c.Outbounds))
|
||||
for i, entry := range c.Outbounds {
|
||||
if entry.Name == "" {
|
||||
return nil, fmt.Errorf("outbounds.name empty outbound name")
|
||||
return nil, fmt.Errorf("empty outbound name")
|
||||
}
|
||||
var ob outbounds.PluggableOutbound
|
||||
var err error
|
||||
@@ -183,33 +200,89 @@ func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound
|
||||
}
|
||||
var uOb outbounds.PluggableOutbound // "unified" outbound
|
||||
|
||||
// ACL
|
||||
hasACL := false
|
||||
if hasACL {
|
||||
// todo fix ACL
|
||||
if c.ACL.File != "" && len(c.ACL.Inline) > 0 {
|
||||
return nil, fmt.Errorf("cannot set both acl.file and acl.inline")
|
||||
}
|
||||
gLoader := &GeoLoader{
|
||||
GeoIPFilename: c.ACL.GeoIP,
|
||||
GeoSiteFilename: c.ACL.GeoSite,
|
||||
UpdateInterval: c.ACL.GeoUpdateInterval,
|
||||
Logger: n.Logger,
|
||||
}
|
||||
|
||||
if c.ACL.File != "" {
|
||||
hasACL = true
|
||||
acl, err := outbounds.NewACLEngineFromFile(c.ACL.File, obs, gLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uOb = acl
|
||||
} else if len(c.ACL.Inline) > 0 {
|
||||
n.Logger.Debug("found ACL Inline:", zap.Strings("Inline", c.ACL.Inline))
|
||||
hasACL = true
|
||||
acl, err := outbounds.NewACLEngineFromString(strings.Join(c.ACL.Inline, "\n"), obs, gLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uOb = acl
|
||||
} else {
|
||||
// No ACL, use the first outbound
|
||||
uOb = obs[0].Outbound
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.Resolver.Type) {
|
||||
case "", "system":
|
||||
if hasACL {
|
||||
// If the user uses ACL, we must put a resolver in front of it,
|
||||
// for IP rules to work on domain requests.
|
||||
uOb = outbounds.NewSystemResolver(uOb)
|
||||
}
|
||||
// Otherwise we can just rely on outbound handling on its own.
|
||||
case "tcp":
|
||||
if c.Resolver.TCP.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverTCP(c.Resolver.TCP.Addr, c.Resolver.TCP.Timeout, uOb)
|
||||
case "udp":
|
||||
if c.Resolver.UDP.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverUDP(c.Resolver.UDP.Addr, c.Resolver.UDP.Timeout, uOb)
|
||||
case "tls", "tcp-tls":
|
||||
if c.Resolver.TLS.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverTLS(c.Resolver.TLS.Addr, c.Resolver.TLS.Timeout, c.Resolver.TLS.SNI, c.Resolver.TLS.Insecure, uOb)
|
||||
case "https", "http":
|
||||
if c.Resolver.HTTPS.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewDoHResolver(c.Resolver.HTTPS.Addr, c.Resolver.HTTPS.Timeout, c.Resolver.HTTPS.SNI, c.Resolver.HTTPS.Insecure, uOb)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported resolver type")
|
||||
}
|
||||
Outbound := &outbounds.PluggableOutboundAdapter{PluggableOutbound: uOb}
|
||||
|
||||
return Outbound, nil
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, info *panel.NodeInfo, config *conf.Options) (http.Handler, error) {
|
||||
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, info *panel.NodeInfo, c *serverConfig) (http.Handler, error) {
|
||||
var handler http.Handler
|
||||
switch strings.ToLower(config.Hysteria2Options.Masquerade.Type) {
|
||||
switch strings.ToLower(c.Masquerade.Type) {
|
||||
case "", "404":
|
||||
handler = http.NotFoundHandler()
|
||||
case "file":
|
||||
if config.Hysteria2Options.Masquerade.File.Dir == "" {
|
||||
if c.Masquerade.File.Dir == "" {
|
||||
return nil, fmt.Errorf("masquerade.file.dir empty file directory")
|
||||
}
|
||||
handler = http.FileServer(http.Dir(config.Hysteria2Options.Masquerade.File.Dir))
|
||||
handler = http.FileServer(http.Dir(c.Masquerade.File.Dir))
|
||||
case "proxy":
|
||||
if config.Hysteria2Options.Masquerade.Proxy.URL == "" {
|
||||
if c.Masquerade.Proxy.URL == "" {
|
||||
return nil, fmt.Errorf("masquerade.proxy.url empty proxy url")
|
||||
}
|
||||
u, err := url.Parse(config.Hysteria2Options.Masquerade.Proxy.URL)
|
||||
u, err := url.Parse(c.Masquerade.Proxy.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("masquerade.proxy.url %s", err))
|
||||
}
|
||||
@@ -218,7 +291,7 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
|
||||
r.SetURL(u)
|
||||
// SetURL rewrites the Host header,
|
||||
// but we don't want that if rewriteHost is false
|
||||
if !config.Hysteria2Options.Masquerade.Proxy.RewriteHost {
|
||||
if !c.Masquerade.Proxy.RewriteHost {
|
||||
r.Out.Host = r.In.Host
|
||||
}
|
||||
},
|
||||
@@ -228,52 +301,88 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
|
||||
},
|
||||
}
|
||||
case "string":
|
||||
if config.Hysteria2Options.Masquerade.String.Content == "" {
|
||||
if c.Masquerade.String.Content == "" {
|
||||
return nil, fmt.Errorf("masquerade.string.content empty string content")
|
||||
}
|
||||
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 &&
|
||||
(config.Hysteria2Options.Masquerade.String.StatusCode < 200 ||
|
||||
config.Hysteria2Options.Masquerade.String.StatusCode > 599 ||
|
||||
config.Hysteria2Options.Masquerade.String.StatusCode == 233) {
|
||||
if c.Masquerade.String.StatusCode != 0 &&
|
||||
(c.Masquerade.String.StatusCode < 200 ||
|
||||
c.Masquerade.String.StatusCode > 599 ||
|
||||
c.Masquerade.String.StatusCode == 233) {
|
||||
// 233 is reserved for Hysteria authentication
|
||||
return nil, fmt.Errorf("masquerade.string.statusCode invalid status code (must be 200-599, except 233)")
|
||||
}
|
||||
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for k, v := range config.Hysteria2Options.Masquerade.String.Headers {
|
||||
for k, v := range c.Masquerade.String.Headers {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 {
|
||||
w.WriteHeader(config.Hysteria2Options.Masquerade.String.StatusCode)
|
||||
if c.Masquerade.String.StatusCode != 0 {
|
||||
w.WriteHeader(c.Masquerade.String.StatusCode)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK) // Use 200 OK by default
|
||||
}
|
||||
_, _ = w.Write([]byte(config.Hysteria2Options.Masquerade.String.Content))
|
||||
_, _ = w.Write([]byte(c.Masquerade.String.Content))
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("masquerade.type unsupported masquerade type")
|
||||
}
|
||||
MasqHandler := &masqHandlerLogWrapper{H: handler, QUIC: true, Logger: n.Logger}
|
||||
|
||||
if config.Hysteria2Options.Masquerade.ListenHTTP != "" || config.Hysteria2Options.Masquerade.ListenHTTPS != "" {
|
||||
if config.Hysteria2Options.Masquerade.ListenHTTP != "" && config.Hysteria2Options.Masquerade.ListenHTTPS == "" {
|
||||
if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
|
||||
if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
|
||||
return nil, fmt.Errorf("masquerade.listenHTTPS having only HTTP server without HTTPS is not supported")
|
||||
}
|
||||
s := masq.MasqTCPServer{
|
||||
QUICPort: extractPortFromAddr(conn.LocalAddr().String()),
|
||||
HTTPSPort: extractPortFromAddr(config.Hysteria2Options.Masquerade.ListenHTTPS),
|
||||
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
|
||||
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: tlsconfig.Certificates,
|
||||
GetCertificate: tlsconfig.GetCertificate,
|
||||
},
|
||||
ForceHTTPS: config.Hysteria2Options.Masquerade.ForceHTTPS,
|
||||
ForceHTTPS: c.Masquerade.ForceHTTPS,
|
||||
}
|
||||
go runMasqTCPServer(&s, config.Hysteria2Options.Masquerade.ListenHTTP, config.Hysteria2Options.Masquerade.ListenHTTPS, n.Logger)
|
||||
go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS, n.Logger)
|
||||
}
|
||||
|
||||
return MasqHandler, nil
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getHyConfig(tag string, info *panel.NodeInfo, config *conf.Options, c *serverConfig) (*server.Config, error) {
|
||||
tls, err := n.getTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quic, err := n.getQUICConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := n.getConn(info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Outbound, err := n.getOutboundConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Masq, err := n.getMasqHandler(tls, conn, info, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &server.Config{
|
||||
TLSConfig: *tls,
|
||||
QUICConfig: *quic,
|
||||
Conn: conn,
|
||||
Outbound: Outbound,
|
||||
BandwidthConfig: *n.getBandwidthConfig(info),
|
||||
IgnoreClientBandwidth: c.IgnoreClientBandwidth,
|
||||
DisableUDP: c.DisableUDP,
|
||||
UDPIdleTimeout: c.UDPIdleTimeout,
|
||||
EventLogger: n.EventLogger,
|
||||
TrafficLogger: n.TrafficLogger,
|
||||
MasqHandler: Masq,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string, logger *zap.Logger) {
|
||||
errChan := make(chan error, 2)
|
||||
if httpAddr != "" {
|
||||
|
||||
181
core/hy2/geoloader.go
Normal file
181
core/hy2/geoloader.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/extras/outbounds/acl"
|
||||
"github.com/apernet/hysteria/extras/outbounds/acl/v2geo"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
geoipFilename = "geoip.dat"
|
||||
geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
geositeFilename = "geosite.dat"
|
||||
geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
geoDlTmpPattern = ".hysteria-geoloader.dlpart.*"
|
||||
|
||||
geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days
|
||||
)
|
||||
|
||||
var _ acl.GeoLoader = (*GeoLoader)(nil)
|
||||
|
||||
// GeoLoader provides the on-demand GeoIP/GeoSite database
|
||||
// loading functionality required by the ACL engine.
|
||||
// Empty filenames = automatic download from built-in URLs.
|
||||
type GeoLoader struct {
|
||||
GeoIPFilename string
|
||||
GeoSiteFilename string
|
||||
UpdateInterval time.Duration
|
||||
|
||||
geoipMap map[string]*v2geo.GeoIP
|
||||
geositeMap map[string]*v2geo.GeoSite
|
||||
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func (l *GeoLoader) shouldDownload(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
if info.Size() == 0 {
|
||||
// empty files are loadable by v2geo, but we consider it broken
|
||||
return true
|
||||
}
|
||||
dt := time.Since(info.ModTime())
|
||||
if l.UpdateInterval == 0 {
|
||||
return dt > geoDefaultUpdateInterval
|
||||
} else {
|
||||
return dt > l.UpdateInterval
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GeoLoader) downloadAndCheck(filename, url string, checkFunc func(filename string) error) error {
|
||||
l.geoDownloadFunc(filename, url)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.CreateTemp(".", geoDlTmpPattern)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
err = checkFunc(f.Name())
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(fmt.Errorf("integrity check failed: %w", err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(f.Name(), filename)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(fmt.Errorf("rename failed: %w", err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
|
||||
if l.geoipMap != nil {
|
||||
return l.geoipMap, nil
|
||||
}
|
||||
autoDL := false
|
||||
filename := l.GeoIPFilename
|
||||
if filename == "" {
|
||||
autoDL = true
|
||||
filename = geoipFilename
|
||||
}
|
||||
if autoDL {
|
||||
if !l.shouldDownload(filename) {
|
||||
m, err := v2geo.LoadGeoIP(filename)
|
||||
if err == nil {
|
||||
l.geoipMap = m
|
||||
return m, nil
|
||||
}
|
||||
// file is broken, download it again
|
||||
}
|
||||
err := l.downloadAndCheck(filename, geoipURL, func(filename string) error {
|
||||
_, err := v2geo.LoadGeoIP(filename)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// as long as the previous download exists, fallback to it
|
||||
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
m, err := v2geo.LoadGeoIP(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.geoipMap = m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (l *GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
|
||||
if l.geositeMap != nil {
|
||||
return l.geositeMap, nil
|
||||
}
|
||||
autoDL := false
|
||||
filename := l.GeoSiteFilename
|
||||
if filename == "" {
|
||||
autoDL = true
|
||||
filename = geositeFilename
|
||||
}
|
||||
if autoDL {
|
||||
if !l.shouldDownload(filename) {
|
||||
m, err := v2geo.LoadGeoSite(filename)
|
||||
if err == nil {
|
||||
l.geositeMap = m
|
||||
return m, nil
|
||||
}
|
||||
// file is broken, download it again
|
||||
}
|
||||
err := l.downloadAndCheck(filename, geositeURL, func(filename string) error {
|
||||
_, err := v2geo.LoadGeoSite(filename)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// as long as the previous download exists, fallback to it
|
||||
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
m, err := v2geo.LoadGeoSite(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.geositeMap = m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (l *GeoLoader) geoDownloadFunc(filename, url string) {
|
||||
l.Logger.Info("downloading database", zap.String("filename", filename), zap.String("url", url))
|
||||
}
|
||||
|
||||
func (l *GeoLoader) geoDownloadErrFunc(err error) {
|
||||
if err != nil {
|
||||
l.Logger.Error("failed to download database", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,11 @@ package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -19,54 +18,21 @@ type Hysteria2node struct {
|
||||
TrafficLogger server.TrafficLogger
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getHyConfig(tag string, info *panel.NodeInfo, config *conf.Options) (*server.Config, error) {
|
||||
tls, err := n.getTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quic, err := n.getQUICConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := n.getConn(info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Outbound, err := n.getOutboundConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Masq, err := n.getMasqHandler(tls, conn, info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &server.Config{
|
||||
TLSConfig: *tls,
|
||||
QUICConfig: *quic,
|
||||
Conn: conn,
|
||||
Outbound: Outbound,
|
||||
BandwidthConfig: *n.getBandwidthConfig(info),
|
||||
IgnoreClientBandwidth: config.Hysteria2Options.IgnoreClientBandwidth,
|
||||
DisableUDP: config.Hysteria2Options.DisableUDP,
|
||||
UDPIdleTimeout: config.Hysteria2Options.UDPIdleTimeout,
|
||||
EventLogger: n.EventLogger,
|
||||
TrafficLogger: n.TrafficLogger,
|
||||
MasqHandler: Masq,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
|
||||
var err error
|
||||
hyconfig := &server.Config{}
|
||||
if len(config.Hysteria2Options.Hysteria2ConfigPath) != 0 {
|
||||
data, err := os.ReadFile(config.Hysteria2Options.Hysteria2ConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read hysteria2 config error: %s", err)
|
||||
var c serverConfig
|
||||
v := viper.New()
|
||||
if len(config.Hysteria2ConfigPath) != 0 {
|
||||
v.SetConfigFile(config.Hysteria2ConfigPath)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
h.Logger.Fatal("failed to read server config", zap.Error(err))
|
||||
}
|
||||
err = json.Unmarshal(data, hyconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal original config error: %s", err)
|
||||
if err := v.Unmarshal(&c); err != nil {
|
||||
h.Logger.Fatal("failed to parse server config", zap.Error(err))
|
||||
}
|
||||
h.Logger.Debug("loaded server config:")
|
||||
fmt.Printf("%+v", c)
|
||||
}
|
||||
n := Hysteria2node{
|
||||
Tag: tag,
|
||||
@@ -80,7 +46,7 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
|
||||
},
|
||||
}
|
||||
|
||||
hyconfig, err = n.getHyConfig(tag, info, config)
|
||||
hyconfig, err = n.getHyConfig(tag, info, config, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/extras/outbounds"
|
||||
)
|
||||
|
||||
func serverConfigOutboundDirectToOutbound(c conf.ServerConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
|
||||
var mode outbounds.DirectOutboundMode
|
||||
switch strings.ToLower(c.Mode) {
|
||||
case "", "auto":
|
||||
mode = outbounds.DirectOutboundModeAuto
|
||||
case "64":
|
||||
mode = outbounds.DirectOutboundMode64
|
||||
case "46":
|
||||
mode = outbounds.DirectOutboundMode46
|
||||
case "6":
|
||||
mode = outbounds.DirectOutboundMode6
|
||||
case "4":
|
||||
mode = outbounds.DirectOutboundMode4
|
||||
default:
|
||||
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
|
||||
}
|
||||
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
|
||||
bindDevice := len(c.BindDevice) > 0
|
||||
if bindIP && bindDevice {
|
||||
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
|
||||
}
|
||||
if bindIP {
|
||||
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
|
||||
if len(c.BindIPv4) > 0 && ip4 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
|
||||
}
|
||||
if len(c.BindIPv6) > 0 && ip6 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
|
||||
}
|
||||
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
|
||||
}
|
||||
if bindDevice {
|
||||
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
|
||||
}
|
||||
return outbounds.NewDirectOutboundSimple(mode), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundSOCKS5ToOutbound(c conf.ServerConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
|
||||
if c.Addr == "" {
|
||||
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
|
||||
}
|
||||
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundHTTPToOutbound(c conf.ServerConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
|
||||
if c.URL == "" {
|
||||
return nil, fmt.Errorf("outbounds.http.url empty http address")
|
||||
}
|
||||
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
|
||||
}
|
||||
228
core/hy2/serverConfig.go
Normal file
228
core/hy2/serverConfig.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/extras/outbounds"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
Listen string `mapstructure:"listen"`
|
||||
Obfs serverConfigObfs `mapstructure:"obfs"`
|
||||
TLS *serverConfigTLS `mapstructure:"tls"`
|
||||
ACME *serverConfigACME `mapstructure:"acme"`
|
||||
QUIC serverConfigQUIC `mapstructure:"quic"`
|
||||
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
|
||||
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
|
||||
DisableUDP bool `mapstructure:"disableUDP"`
|
||||
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
|
||||
Auth serverConfigAuth `mapstructure:"auth"`
|
||||
Resolver serverConfigResolver `mapstructure:"resolver"`
|
||||
ACL serverConfigACL `mapstructure:"acl"`
|
||||
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
|
||||
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
|
||||
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
||||
}
|
||||
|
||||
type serverConfigObfsSalamander struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
type serverConfigObfs struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Salamander serverConfigObfsSalamander `mapstructure:"salamander"`
|
||||
}
|
||||
|
||||
type serverConfigTLS struct {
|
||||
Cert string `mapstructure:"cert"`
|
||||
Key string `mapstructure:"key"`
|
||||
}
|
||||
|
||||
type serverConfigACME struct {
|
||||
Domains []string `mapstructure:"domains"`
|
||||
Email string `mapstructure:"email"`
|
||||
CA string `mapstructure:"ca"`
|
||||
DisableHTTP bool `mapstructure:"disableHTTP"`
|
||||
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
|
||||
AltHTTPPort int `mapstructure:"altHTTPPort"`
|
||||
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
|
||||
Dir string `mapstructure:"dir"`
|
||||
}
|
||||
|
||||
type serverConfigQUIC struct {
|
||||
InitStreamReceiveWindow uint64 `mapstructure:"initStreamReceiveWindow"`
|
||||
MaxStreamReceiveWindow uint64 `mapstructure:"maxStreamReceiveWindow"`
|
||||
InitConnectionReceiveWindow uint64 `mapstructure:"initConnReceiveWindow"`
|
||||
MaxConnectionReceiveWindow uint64 `mapstructure:"maxConnReceiveWindow"`
|
||||
MaxIdleTimeout time.Duration `mapstructure:"maxIdleTimeout"`
|
||||
MaxIncomingStreams int64 `mapstructure:"maxIncomingStreams"`
|
||||
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
|
||||
}
|
||||
|
||||
type serverConfigBandwidth struct {
|
||||
Up string `mapstructure:"up"`
|
||||
Down string `mapstructure:"down"`
|
||||
}
|
||||
|
||||
type serverConfigAuthHTTP struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigAuth struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Password string `mapstructure:"password"`
|
||||
UserPass map[string]string `mapstructure:"userpass"`
|
||||
HTTP serverConfigAuthHTTP `mapstructure:"http"`
|
||||
Command string `mapstructure:"command"`
|
||||
}
|
||||
|
||||
type serverConfigResolverTCP struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
type serverConfigResolverUDP struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
type serverConfigResolverTLS struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
SNI string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolverHTTPS struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
SNI string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolver struct {
|
||||
Type string `mapstructure:"type"`
|
||||
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
||||
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
||||
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
||||
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
||||
}
|
||||
|
||||
type serverConfigACL struct {
|
||||
File string `mapstructure:"file"`
|
||||
Inline []string `mapstructure:"inline"`
|
||||
GeoIP string `mapstructure:"geoip"`
|
||||
GeoSite string `mapstructure:"geosite"`
|
||||
GeoUpdateInterval time.Duration `mapstructure:"geoUpdateInterval"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundDirect struct {
|
||||
Mode string `mapstructure:"mode"`
|
||||
BindIPv4 string `mapstructure:"bindIPv4"`
|
||||
BindIPv6 string `mapstructure:"bindIPv6"`
|
||||
BindDevice string `mapstructure:"bindDevice"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundSOCKS5 struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundHTTP struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundEntry struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Type string `mapstructure:"type"`
|
||||
Direct serverConfigOutboundDirect `mapstructure:"direct"`
|
||||
SOCKS5 serverConfigOutboundSOCKS5 `mapstructure:"socks5"`
|
||||
HTTP serverConfigOutboundHTTP `mapstructure:"http"`
|
||||
}
|
||||
|
||||
type serverConfigTrafficStats struct {
|
||||
Listen string `mapstructure:"listen"`
|
||||
Secret string `mapstructure:"secret"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeFile struct {
|
||||
Dir string `mapstructure:"dir"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeProxy struct {
|
||||
URL string `mapstructure:"url"`
|
||||
RewriteHost bool `mapstructure:"rewriteHost"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeString struct {
|
||||
Content string `mapstructure:"content"`
|
||||
Headers map[string]string `mapstructure:"headers"`
|
||||
StatusCode int `mapstructure:"statusCode"`
|
||||
}
|
||||
|
||||
type serverConfigMasquerade struct {
|
||||
Type string `mapstructure:"type"`
|
||||
File serverConfigMasqueradeFile `mapstructure:"file"`
|
||||
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
|
||||
String serverConfigMasqueradeString `mapstructure:"string"`
|
||||
ListenHTTP string `mapstructure:"listenHTTP"`
|
||||
ListenHTTPS string `mapstructure:"listenHTTPS"`
|
||||
ForceHTTPS bool `mapstructure:"forceHTTPS"`
|
||||
}
|
||||
|
||||
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
|
||||
var mode outbounds.DirectOutboundMode
|
||||
switch strings.ToLower(c.Mode) {
|
||||
case "", "auto":
|
||||
mode = outbounds.DirectOutboundModeAuto
|
||||
case "64":
|
||||
mode = outbounds.DirectOutboundMode64
|
||||
case "46":
|
||||
mode = outbounds.DirectOutboundMode46
|
||||
case "6":
|
||||
mode = outbounds.DirectOutboundMode6
|
||||
case "4":
|
||||
mode = outbounds.DirectOutboundMode4
|
||||
default:
|
||||
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
|
||||
}
|
||||
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
|
||||
bindDevice := len(c.BindDevice) > 0
|
||||
if bindIP && bindDevice {
|
||||
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
|
||||
}
|
||||
if bindIP {
|
||||
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
|
||||
if len(c.BindIPv4) > 0 && ip4 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
|
||||
}
|
||||
if len(c.BindIPv6) > 0 && ip6 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
|
||||
}
|
||||
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
|
||||
}
|
||||
if bindDevice {
|
||||
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
|
||||
}
|
||||
return outbounds.NewDirectOutboundSimple(mode), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
|
||||
if c.Addr == "" {
|
||||
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
|
||||
}
|
||||
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundHTTPToOutbound(c serverConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
|
||||
if c.URL == "" {
|
||||
return nil, fmt.Errorf("outbounds.http.url empty http address")
|
||||
}
|
||||
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
|
||||
}
|
||||
Reference in New Issue
Block a user