mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 12:40:11 +00:00
测试:增加hysteria2内核
This commit is contained in:
316
core/hy2/config.go
Normal file
316
core/hy2/config.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/extras/correctnet"
|
||||
"github.com/apernet/hysteria/extras/masq"
|
||||
"github.com/apernet/hysteria/extras/obfs"
|
||||
"github.com/apernet/hysteria/extras/outbounds"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type masqHandlerLogWrapper struct {
|
||||
H http.Handler
|
||||
QUIC bool
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func (m *masqHandlerLogWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
m.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)
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
Kilobyte = Byte * 1000
|
||||
Megabyte = Kilobyte * 1000
|
||||
Gigabyte = Megabyte * 1000
|
||||
Terabyte = Gigabyte * 1000
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStreamReceiveWindow = 8388608 // 8MB
|
||||
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB
|
||||
defaultMaxIdleTimeout = 30 * time.Second
|
||||
defaultMaxIncomingStreams = 1024
|
||||
defaultUDPIdleTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (n *Hysteria2node) getTLSConfig(config *conf.Options) (*server.TLSConfig, error) {
|
||||
if config.CertConfig == nil {
|
||||
return nil, fmt.Errorf("the CertConfig is not vail")
|
||||
}
|
||||
switch config.CertConfig.CertMode {
|
||||
case "none", "":
|
||||
return nil, fmt.Errorf("the CertMode cannot be none")
|
||||
default:
|
||||
var certs []tls.Certificate
|
||||
cert, err := tls.LoadX509KeyPair(config.CertConfig.CertFile, config.CertConfig.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
return &server.TLSConfig{
|
||||
Certificates: certs,
|
||||
GetCertificate: func(tlsinfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(config.CertConfig.CertFile, config.CertConfig.KeyFile)
|
||||
return &cert, err
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getQUICConfig(config *conf.Options) (*server.QUICConfig, error) {
|
||||
quic := &server.QUICConfig{}
|
||||
if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow == 0 {
|
||||
quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384")
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow == 0 {
|
||||
quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384")
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow == 0 {
|
||||
quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384")
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow == 0 {
|
||||
quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384")
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIdleTimeout == 0 {
|
||||
quic.MaxIdleTimeout = defaultMaxIdleTimeout
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIdleTimeout < 4*time.Second || config.Hysteria2Options.QUICConfig.MaxIdleTimeout > 120*time.Second {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s")
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIncomingStreams == 0 {
|
||||
quic.MaxIncomingStreams = defaultMaxIncomingStreams
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIncomingStreams < 8 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8")
|
||||
}
|
||||
// todo fix !linux && !windows && !darwin
|
||||
quic.DisablePathMTUDiscovery = false
|
||||
|
||||
return quic, nil
|
||||
}
|
||||
func (n *Hysteria2node) getConn(info *panel.NodeInfo, config *conf.Options) (net.PacketConn, error) {
|
||||
uAddr, err := net.ResolveUDPAddr("udp", formatAddress(config.ListenIP, info.Common.ServerPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := correctnet.ListenUDP("udp", uAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch strings.ToLower(info.Hysteria2.ObfsType) {
|
||||
case "", "plain":
|
||||
return conn, nil
|
||||
case "salamander":
|
||||
ob, err := obfs.NewSalamanderObfuscator([]byte(info.Hysteria2.ObfsPassword))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obfs.WrapPacketConn(conn, ob), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported obfuscation type")
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.BandwidthConfig {
|
||||
band := &server.BandwidthConfig{}
|
||||
if info.Hysteria2.UpMbps != 0 {
|
||||
band.MaxTx = (uint64)(info.Hysteria2.UpMbps * Megabyte / 8)
|
||||
}
|
||||
if info.Hysteria2.DownMbps != 0 {
|
||||
band.MaxRx = (uint64)(info.Hysteria2.DownMbps * Megabyte / 8)
|
||||
|
||||
}
|
||||
return band
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound, error) {
|
||||
var obs []outbounds.OutboundEntry
|
||||
if len(config.Hysteria2Options.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 {
|
||||
if entry.Name == "" {
|
||||
return nil, fmt.Errorf("outbounds.name empty outbound name")
|
||||
}
|
||||
var ob outbounds.PluggableOutbound
|
||||
var err error
|
||||
switch strings.ToLower(entry.Type) {
|
||||
case "direct":
|
||||
ob, err = serverConfigOutboundDirectToOutbound(entry.Direct)
|
||||
case "socks5":
|
||||
ob, err = serverConfigOutboundSOCKS5ToOutbound(entry.SOCKS5)
|
||||
case "http":
|
||||
ob, err = serverConfigOutboundHTTPToOutbound(entry.HTTP)
|
||||
default:
|
||||
err = fmt.Errorf("outbounds.type unsupported outbound type")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obs[i] = outbounds.OutboundEntry{Name: entry.Name, Outbound: ob}
|
||||
}
|
||||
}
|
||||
var uOb outbounds.PluggableOutbound // "unified" outbound
|
||||
|
||||
hasACL := false
|
||||
if hasACL {
|
||||
// todo fix ACL
|
||||
} else {
|
||||
// No ACL, use the first outbound
|
||||
uOb = obs[0].Outbound
|
||||
}
|
||||
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) {
|
||||
var handler http.Handler
|
||||
switch strings.ToLower(config.Hysteria2Options.Masquerade.Type) {
|
||||
case "", "404":
|
||||
handler = http.NotFoundHandler()
|
||||
case "file":
|
||||
if config.Hysteria2Options.Masquerade.File.Dir == "" {
|
||||
return nil, fmt.Errorf("masquerade.file.dir empty file directory")
|
||||
}
|
||||
handler = http.FileServer(http.Dir(config.Hysteria2Options.Masquerade.File.Dir))
|
||||
case "proxy":
|
||||
if config.Hysteria2Options.Masquerade.Proxy.URL == "" {
|
||||
return nil, fmt.Errorf("masquerade.proxy.url empty proxy url")
|
||||
}
|
||||
u, err := url.Parse(config.Hysteria2Options.Masquerade.Proxy.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("masquerade.proxy.url %s", err))
|
||||
}
|
||||
handler = &httputil.ReverseProxy{
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
r.SetURL(u)
|
||||
// SetURL rewrites the Host header,
|
||||
// but we don't want that if rewriteHost is false
|
||||
if !config.Hysteria2Options.Masquerade.Proxy.RewriteHost {
|
||||
r.Out.Host = r.In.Host
|
||||
}
|
||||
},
|
||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
n.Logger.Error("HTTP reverse proxy error", zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
},
|
||||
}
|
||||
case "string":
|
||||
if config.Hysteria2Options.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) {
|
||||
// 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 {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 {
|
||||
w.WriteHeader(config.Hysteria2Options.Masquerade.String.StatusCode)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK) // Use 200 OK by default
|
||||
}
|
||||
_, _ = w.Write([]byte(config.Hysteria2Options.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 == "" {
|
||||
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),
|
||||
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: tlsconfig.Certificates,
|
||||
GetCertificate: tlsconfig.GetCertificate,
|
||||
},
|
||||
ForceHTTPS: config.Hysteria2Options.Masquerade.ForceHTTPS,
|
||||
}
|
||||
go runMasqTCPServer(&s, config.Hysteria2Options.Masquerade.ListenHTTP, config.Hysteria2Options.Masquerade.ListenHTTPS, n.Logger)
|
||||
}
|
||||
|
||||
return MasqHandler, nil
|
||||
}
|
||||
|
||||
func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string, logger *zap.Logger) {
|
||||
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 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
|
||||
}
|
||||
|
||||
func formatAddress(ip string, port int) string {
|
||||
// 检查 IP 地址是否为 IPv6
|
||||
if strings.Contains(ip, ":") {
|
||||
return fmt.Sprintf("[%s]:%d", ip, port)
|
||||
}
|
||||
// 对于 IPv4 地址,直接返回 IP:Port 格式
|
||||
return fmt.Sprintf("%s:%d", ip, port)
|
||||
}
|
||||
26
core/hy2/hook.go
Normal file
26
core/hy2/hook.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/InazumaV/V2bX/common/counter"
|
||||
)
|
||||
|
||||
type HookServer struct {
|
||||
Tag string
|
||||
Counter sync.Map
|
||||
}
|
||||
|
||||
func (h *HookServer) Log(id string, tx, rx uint64) (ok bool) {
|
||||
if c, ok := h.Counter.Load(h.Tag); ok {
|
||||
c.(*counter.TrafficCounter).Rx(id, int(rx))
|
||||
c.(*counter.TrafficCounter).Tx(id, int(rx))
|
||||
return true
|
||||
} else {
|
||||
c := counter.NewTrafficCounter()
|
||||
h.Counter.Store(h.Tag, c)
|
||||
c.Rx(id, int(rx))
|
||||
c.Tx(id, int(rx))
|
||||
return true
|
||||
}
|
||||
}
|
||||
61
core/hy2/hy2.go
Normal file
61
core/hy2/hy2.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
vCore "github.com/InazumaV/V2bX/core"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ vCore.Core = (*Hysteria2)(nil)
|
||||
|
||||
type Hysteria2 struct {
|
||||
Hy2nodes map[string]Hysteria2node
|
||||
Auth *V2bX
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func init() {
|
||||
vCore.RegisterCore("hysteria2", New)
|
||||
}
|
||||
|
||||
func New(c *conf.CoreConfig) (vCore.Core, error) {
|
||||
loglever := "error"
|
||||
if c.Hysteria2Config.LogConfig.Level != "" {
|
||||
loglever = c.Hysteria2Config.LogConfig.Level
|
||||
}
|
||||
log, err := initLogger(loglever, "console")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Hysteria2{
|
||||
Hy2nodes: make(map[string]Hysteria2node),
|
||||
Auth: &V2bX{
|
||||
usersMap: make(map[string]int),
|
||||
},
|
||||
Logger: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) Protocols() []string {
|
||||
return []string{
|
||||
"hysteria2",
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hysteria2) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) Close() error {
|
||||
for _, n := range h.Hy2nodes {
|
||||
err := n.Hy2server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) Type() string {
|
||||
return "hysteria2"
|
||||
}
|
||||
137
core/hy2/logger.go
Normal file
137
core/hy2/logger.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/InazumaV/V2bX/limiter"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type serverLogger struct {
|
||||
Tag string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
var logLevelMap = map[string]zapcore.Level{
|
||||
"debug": zapcore.DebugLevel,
|
||||
"info": zapcore.InfoLevel,
|
||||
"warn": zapcore.WarnLevel,
|
||||
"error": zapcore.ErrorLevel,
|
||||
}
|
||||
|
||||
var logFormatMap = map[string]zapcore.EncoderConfig{
|
||||
"console": {
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
MessageKey: "msg",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||||
EncodeTime: zapcore.RFC3339TimeEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
},
|
||||
"json": {
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
MessageKey: "msg",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.EpochMillisTimeEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
},
|
||||
}
|
||||
|
||||
func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
}
|
||||
l.logger.Info("client connected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint64("tx", tx))
|
||||
}
|
||||
|
||||
func (l *serverLogger) Disconnect(addr net.Addr, uuid string, err error) {
|
||||
l.logger.Info("client disconnected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Error(err))
|
||||
}
|
||||
|
||||
func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
}
|
||||
l.logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
|
||||
func (l *serverLogger) TCPError(addr net.Addr, uuid, reqAddr string, err error) {
|
||||
if err == nil {
|
||||
l.logger.Debug("TCP closed", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
|
||||
} else {
|
||||
l.logger.Debug("TCP error", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32, reqAddr string) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
}
|
||||
l.logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
|
||||
func (l *serverLogger) UDPError(addr net.Addr, uuid string, sessionId uint32, err error) {
|
||||
if err == nil {
|
||||
l.logger.Debug("UDP closed", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId))
|
||||
} else {
|
||||
l.logger.Debug("UDP error", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
|
||||
level, ok := logLevelMap[strings.ToLower(logLevel)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("unsupported log level: %s\n", logLevel))
|
||||
}
|
||||
enc, ok := logFormatMap[strings.ToLower(logFormat)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("unsupported log format: %s\n", logFormat))
|
||||
}
|
||||
c := zap.Config{
|
||||
Level: zap.NewAtomicLevelAt(level),
|
||||
DisableCaller: true,
|
||||
DisableStacktrace: true,
|
||||
Encoding: strings.ToLower(logFormat),
|
||||
EncoderConfig: enc,
|
||||
OutputPaths: []string{"stderr"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
}
|
||||
logger, err := c.Build()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("failed to initialize logger: %s\n", err))
|
||||
}
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
func extractIPFromAddr(addr net.Addr) string {
|
||||
switch v := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return v.IP.String()
|
||||
case *net.UDPAddr:
|
||||
return v.IP.String()
|
||||
case *net.IPAddr:
|
||||
return v.IP.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
92
core/hy2/node.go
Normal file
92
core/hy2/node.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Hysteria2node struct {
|
||||
Hy2server server.Server
|
||||
Tag string
|
||||
Logger *zap.Logger
|
||||
EventLogger server.EventLogger
|
||||
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 {
|
||||
n := Hysteria2node{
|
||||
Tag: tag,
|
||||
Logger: h.Logger,
|
||||
EventLogger: &serverLogger{
|
||||
Tag: tag,
|
||||
logger: h.Logger,
|
||||
},
|
||||
TrafficLogger: &HookServer{
|
||||
Tag: tag,
|
||||
},
|
||||
}
|
||||
hyconfig, err := n.getHyConfig(tag, info, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hyconfig.Authenticator = h.Auth
|
||||
s, err := server.NewServer(hyconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Hy2server = s
|
||||
h.Hy2nodes[tag] = n
|
||||
go func() {
|
||||
if err := s.Serve(); err != nil {
|
||||
h.Logger.Error("Server Error", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DelNode(tag string) error {
|
||||
err := h.Hy2nodes[tag].Hy2server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(h.Hy2nodes, tag)
|
||||
return nil
|
||||
}
|
||||
61
core/hy2/outbound.go
Normal file
61
core/hy2/outbound.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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)
|
||||
}
|
||||
70
core/hy2/user.go
Normal file
70
core/hy2/user.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/common/counter"
|
||||
vCore "github.com/InazumaV/V2bX/core"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &V2bX{}
|
||||
|
||||
type V2bX struct {
|
||||
usersMap map[string]int
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (v *V2bX) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
if _, exists := v.usersMap[auth]; exists {
|
||||
return true, auth
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (h *Hysteria2) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
|
||||
var wg sync.WaitGroup
|
||||
for _, user := range p.Users {
|
||||
wg.Add(1)
|
||||
go func(u panel.UserInfo) {
|
||||
defer wg.Done()
|
||||
h.Auth.mutex.Lock()
|
||||
h.Auth.usersMap[u.Uuid] = u.Id
|
||||
h.Auth.mutex.Unlock()
|
||||
}(user)
|
||||
}
|
||||
wg.Wait()
|
||||
return len(p.Users), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, user := range users {
|
||||
wg.Add(1)
|
||||
go func(u panel.UserInfo) {
|
||||
defer wg.Done()
|
||||
h.Auth.mutex.Lock()
|
||||
delete(h.Auth.usersMap, u.Uuid)
|
||||
h.Auth.mutex.Unlock()
|
||||
}(user)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) GetUserTraffic(tag string, uuid string, reset bool) (up int64, down int64) {
|
||||
if v, ok := h.Hy2nodes[tag].TrafficLogger.(*HookServer).Counter.Load(tag); ok {
|
||||
c := v.(*counter.TrafficCounter)
|
||||
up = c.GetUpCount(uuid)
|
||||
down = c.GetDownCount(uuid)
|
||||
if reset {
|
||||
c.Reset(uuid)
|
||||
}
|
||||
return up, down
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
Reference in New Issue
Block a user