support hysteria for sing, remove hy core

This commit is contained in:
yuzuki999
2023-08-08 18:24:10 +08:00
parent 9495913f26
commit 69aed47086
17 changed files with 32 additions and 844 deletions

View File

@@ -1,26 +0,0 @@
package hy
const (
mbpsToBps = 125000
minSpeedBPS = 16384
DefaultALPN = "hysteria"
DefaultStreamReceiveWindow = 16777216 // 16 MB
DefaultConnectionReceiveWindow = DefaultStreamReceiveWindow * 5 / 2 // 40 MB
DefaultMaxIncomingStreams = 1024
DefaultMMDBFilename = "GeoLite2-Country.mmdb"
ServerMaxIdleTimeoutSec = 60
DefaultClientIdleTimeoutSec = 20
DefaultClientHopIntervalSec = 10
)
func SpeedTrans(upM, downM int) (uint64, uint64) {
up := uint64(upM) * mbpsToBps
down := uint64(downM) * mbpsToBps
return up, down
}

View File

@@ -1,49 +0,0 @@
package hy
import (
"fmt"
"sync"
"github.com/InazumaV/V2bX/conf"
vCore "github.com/InazumaV/V2bX/core"
"github.com/hashicorp/go-multierror"
)
func init() {
vCore.RegisterCore("hy", NewHy)
}
type Hy struct {
servers sync.Map
}
func NewHy(_ *conf.CoreConfig) (vCore.Core, error) {
return &Hy{
servers: sync.Map{},
}, nil
}
func (h *Hy) Start() error {
return nil
}
func (h *Hy) Close() error {
var errs error
h.servers.Range(func(tag, s any) bool {
err := s.(*Server).Close()
if err != nil {
errs = multierror.Append(errs, fmt.Errorf("close %s error: %s", tag, err))
}
return true
})
if errs != nil {
return errs
}
return nil
}
func (h *Hy) Protocols() []string {
return []string{
"hysteria",
}
}

View File

@@ -1,43 +0,0 @@
package hy
import (
"net"
)
type ipMasker struct {
IPv4Mask net.IPMask
IPv6Mask net.IPMask
}
// Mask masks an address with the configured CIDR.
// addr can be "host:port" or just host.
func (m *ipMasker) Mask(addr string) string {
if m.IPv4Mask == nil && m.IPv6Mask == nil {
return addr
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
// just host
host, port = addr, ""
}
ip := net.ParseIP(host)
if ip == nil {
// not an IP address, return as is
return addr
}
if ip4 := ip.To4(); ip4 != nil && m.IPv4Mask != nil {
// IPv4
host = ip4.Mask(m.IPv4Mask).String()
} else if ip6 := ip.To16(); ip6 != nil && m.IPv6Mask != nil {
// IPv6
host = ip6.Mask(m.IPv6Mask).String()
}
if port != "" {
return net.JoinHostPort(host, port)
} else {
return host
}
}
var defaultIPMasker = &ipMasker{}

View File

@@ -1,95 +0,0 @@
package hy
import (
"crypto/tls"
"sync"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
type keypairLoader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}
func newKeypairLoader(certPath, keyPath string) (*keypairLoader, error) {
loader := &keypairLoader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
loader.cert = &cert
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
switch event.Op {
case fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Chmod:
logrus.WithFields(logrus.Fields{
"file": event.Name,
}).Info("Keypair change detected, reloading...")
if err := loader.load(); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to reload keypair")
} else {
logrus.Info("Keypair successfully reloaded")
}
case fsnotify.Remove:
_ = watcher.Add(event.Name) // Workaround for vim
// https://github.com/fsnotify/fsnotify/issues/92
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to watch keypair files for changes")
}
}
}()
err = watcher.Add(certPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
err = watcher.Add(keyPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
return loader, nil
}
func (kpr *keypairLoader) load() error {
cert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
kpr.cert = &cert
kpr.certMu.Unlock()
return nil
}
func (kpr *keypairLoader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}

View File

@@ -1,26 +0,0 @@
package hy
import (
"os"
"github.com/oschwald/geoip2-golang"
"github.com/sirupsen/logrus"
)
func loadMMDBReader(filename string) (*geoip2.Reader, error) {
if _, err := os.Stat(filename); err != nil {
if os.IsNotExist(err) {
logrus.Info("GeoLite2 database not found, downloading...")
logrus.WithFields(logrus.Fields{
"file": filename,
}).Info("GeoLite2 database downloaded")
return geoip2.Open(filename)
} else {
// some other error
return nil, err
}
} else {
// file exists, just open it
return geoip2.Open(filename)
}
}

View File

@@ -1,43 +0,0 @@
package hy
import (
"errors"
"fmt"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/InazumaV/V2bX/limiter"
)
func (h *Hy) AddNode(tag string, info *panel.NodeInfo, c *conf.Options) error {
if info.Type != "hysteria" {
return errors.New("the core not support " + info.Type)
}
switch c.CertConfig.CertMode {
case "reality", "none", "":
return errors.New("hysteria need normal tls cert")
}
l, err := limiter.GetLimiter(tag)
if err != nil {
return fmt.Errorf("get limiter error: %s", err)
}
s := NewServer(tag, l)
err = s.runServer(info, c)
if err != nil {
return fmt.Errorf("run hy server error: %s", err)
}
h.servers.Store(tag, s)
return nil
}
func (h *Hy) DelNode(tag string) error {
if s, e := h.servers.Load(tag); e {
err := s.(*Server).Close()
if err != nil {
return err
}
h.servers.Delete(tag)
return nil
}
return errors.New("the node is not have")
}

View File

@@ -1,123 +0,0 @@
package hy
import (
"crypto/tls"
"errors"
"net"
"net/url"
"strings"
"github.com/Yuzuki616/hysteria/core/utils"
rdns "github.com/folbricht/routedns"
)
var errInvalidSyntax = errors.New("invalid syntax")
func setResolver(dns string) error {
if net.ParseIP(dns) != nil {
// Just an IP address, treat as UDP 53
dns = "udp://" + net.JoinHostPort(dns, "53")
}
var r rdns.Resolver
if strings.HasPrefix(dns, "udp://") {
// Standard UDP DNS resolver
dns = strings.TrimPrefix(dns, "udp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-udp", dns, "udp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "tcp://") {
// Standard TCP DNS resolver
dns = strings.TrimPrefix(dns, "tcp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-tcp", dns, "tcp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "https://") {
// DoH resolver
if dohURL, err := url.Parse(dns); err != nil {
return err
} else {
// Need to set bootstrap address to avoid loopback DNS lookup
dohIPAddr, err := net.ResolveIPAddr("ip", dohURL.Hostname())
if err != nil {
return err
}
client, err := rdns.NewDoHClient("doh", dns, rdns.DoHClientOptions{
BootstrapAddr: dohIPAddr.String(),
})
if err != nil {
return err
}
r = client
}
} else if strings.HasPrefix(dns, "tls://") {
// DoT resolver
dns = strings.TrimPrefix(dns, "tls://")
if dns == "" {
return errInvalidSyntax
}
dotHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
dotIPAddr, err := net.ResolveIPAddr("ip", dotHost)
if err != nil {
return err
}
client, err := rdns.NewDoTClient("dot", dns, rdns.DoTClientOptions{
BootstrapAddr: dotIPAddr.String(),
TLSConfig: new(tls.Config),
})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "quic://") {
// DoQ resolver
dns = strings.TrimPrefix(dns, "quic://")
if dns == "" {
return errInvalidSyntax
}
doqHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
doqIPAddr, err := net.ResolveIPAddr("ip", doqHost)
if err != nil {
return err
}
client, err := rdns.NewDoQClient("doq", dns, rdns.DoQClientOptions{
BootstrapAddr: doqIPAddr.String(),
})
if err != nil {
return err
}
r = client
} else {
return errInvalidSyntax
}
cache := rdns.NewCache("cache", r, rdns.CacheOptions{})
net.DefaultResolver = rdns.NewNetResolver(cache)
return nil
}

View File

@@ -1,257 +0,0 @@
package hy
import (
"crypto/tls"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/InazumaV/V2bX/limiter"
"github.com/Yuzuki616/hysteria/core/sockopt"
"github.com/Yuzuki616/quic-go"
"github.com/Yuzuki616/hysteria/core/acl"
"github.com/Yuzuki616/hysteria/core/cs"
"github.com/Yuzuki616/hysteria/core/pktconns"
"github.com/Yuzuki616/hysteria/core/pmtud"
"github.com/Yuzuki616/hysteria/core/transport"
"github.com/sirupsen/logrus"
)
var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFactory{
"": pktconns.NewServerUDPConnFunc,
"udp": pktconns.NewServerUDPConnFunc,
"wechat": pktconns.NewServerWeChatConnFunc,
"wechat-video": pktconns.NewServerWeChatConnFunc,
"faketcp": pktconns.NewServerFakeTCPConnFunc,
}
type Server struct {
tag string
l *limiter.Limiter
counter *counter.TrafficCounter
users sync.Map
running atomic.Bool
*cs.Server
}
func NewServer(tag string, l *limiter.Limiter) *Server {
return &Server{
tag: tag,
l: l,
}
}
func (s *Server) runServer(node *panel.NodeInfo, c *conf.Options) error {
/*if c.HyOptions == nil {
return errors.New("hy options is not vail")
}*/
// Resolver
if len(c.HyOptions.Resolver) > 0 {
err := setResolver(c.HyOptions.Resolver)
if err != nil {
return fmt.Errorf("set resolver error: %s", err)
}
}
// tls config
kpl, err := newKeypairLoader(c.CertConfig.CertFile, c.CertConfig.KeyFile)
if err != nil {
return fmt.Errorf("load cert error: %s", err)
}
tlsConfig := &tls.Config{
GetCertificate: kpl.GetCertificateFunc(),
NextProtos: []string{DefaultALPN},
MinVersion: tls.VersionTLS13,
}
// QUIC config
quicConfig := &quic.Config{
InitialStreamReceiveWindow: DefaultStreamReceiveWindow,
MaxStreamReceiveWindow: DefaultStreamReceiveWindow,
InitialConnectionReceiveWindow: DefaultConnectionReceiveWindow,
MaxConnectionReceiveWindow: DefaultConnectionReceiveWindow,
MaxIncomingStreams: int64(DefaultMaxIncomingStreams),
MaxIdleTimeout: ServerMaxIdleTimeoutSec * time.Second,
KeepAlivePeriod: 0, // Keep alive should solely be client's responsibility
DisablePathMTUDiscovery: false,
EnableDatagrams: true,
}
if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery {
logrus.Info("Path MTU Discovery is not yet supported on this platform")
}
// Resolve preference
if len(c.HyOptions.ResolvePreference) > 0 {
pref, err := transport.ResolvePreferenceFromString(c.HyOptions.Resolver)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to parse the resolve preference")
}
transport.DefaultServerTransport.ResolvePreference = pref
}
/*// SOCKS5 outbound
if config.SOCKS5Outbound.Server != "" {
transport.DefaultServerTransport.SOCKS5Client = transport.NewSOCKS5Client(config.SOCKS5Outbound.Server,
config.SOCKS5Outbound.User, config.SOCKS5Outbound.Password)
}*/
// Bind outbound
if c.HyOptions.SendDevice != "" {
iface, err := net.InterfaceByName(c.HyOptions.SendDevice)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to find the interface")
}
transport.DefaultServerTransport.LocalUDPIntf = iface
sockopt.BindDialer(transport.DefaultServerTransport.Dialer, iface)
}
if c.SendIP != "" {
ip := net.ParseIP(c.SendIP)
if ip == nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to parse the address")
}
transport.DefaultServerTransport.Dialer.LocalAddr = &net.TCPAddr{IP: ip}
transport.DefaultServerTransport.LocalUDPAddr = &net.UDPAddr{IP: ip}
}
// ACL
var aclEngine *acl.Engine
// Prometheus
s.counter = counter.NewTrafficCounter()
// Packet conn
pktConnFuncFactory := serverPacketConnFuncFactoryMap[""]
if pktConnFuncFactory == nil {
return fmt.Errorf("unsopport protocol")
}
pktConnFunc := pktConnFuncFactory(node.HyObfs)
addr := fmt.Sprintf("%s:%d", c.ListenIP, node.Port)
pktConn, err := pktConnFunc(addr)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"addr": addr,
}).Fatal("Failed to listen on the UDP address")
}
// Server
up, down := SpeedTrans(node.UpMbps, node.DownMbps)
s.Server, err = cs.NewServer(tlsConfig, quicConfig, pktConn,
transport.DefaultServerTransport, up, down, false, aclEngine,
s.connectFunc, s.disconnectFunc, tcpRequestFunc, tcpErrorFunc, udpRequestFunc, udpErrorFunc, s.counter)
if err != nil {
return fmt.Errorf("new server error: %s", err)
}
logrus.WithField("addr", addr).Info("Server up and running")
go func() {
s.running.Store(true)
defer func() {
s.running.Store(false)
}()
err = s.Server.Serve()
if err != nil {
logrus.WithField("addr", addr).Errorf("serve error: %s", err)
}
}()
return nil
}
func (s *Server) authByUser(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
if _, r := s.l.CheckLimit(string(auth), addr.String(), false); r {
return false, "device limited"
}
if _, ok := s.users.Load(string(auth)); ok {
return true, "Done"
}
return false, "Failed"
}
func (s *Server) connectFunc(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
s.l.ConnLimiter.AddConnCount(addr.String(), string(auth), false)
ok, msg := s.authByUser(addr, auth, sSend, sRecv)
if !ok {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
}).Info("Authentication failed, client rejected")
return false, msg
}
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"Uuid": string(auth),
"Tag": s.tag,
}).Info("Client connected")
return ok, msg
}
func (s *Server) disconnectFunc(addr net.Addr, auth []byte, err error) {
s.l.ConnLimiter.DelConnCount(addr.String(), string(auth))
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"error": err,
}).Info("Client disconnected")
}
func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string) {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
"action": actionToString(action, arg),
}).Debug("TCP request")
}
func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) {
if err != io.EOF {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
"error": err,
}).Info("TCP error")
} else {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debug("TCP EOF")
}
}
func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
}).Debug("UDP request")
}
func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err error) {
if err != io.EOF {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
"error": err,
}).Info("UDP error")
} else {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
}).Debug("UDP EOF")
}
}
func actionToString(action acl.Action, arg string) string {
switch action {
case acl.ActionDirect:
return "Direct"
case acl.ActionProxy:
return "Proxy"
case acl.ActionBlock:
return "Block"
case acl.ActionHijack:
return "Hijack to " + arg
default:
return "Unknown"
}
}

View File

@@ -1,43 +0,0 @@
package hy
import (
"encoding/base64"
"log"
"testing"
"time"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/InazumaV/V2bX/limiter"
"github.com/sirupsen/logrus"
)
func TestServer(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
limiter.Init()
l := limiter.AddLimiter("test", &conf.LimitConfig{}, nil)
s := NewServer("test", l)
t.Log(s.runServer(&panel.NodeInfo{
Port: 1145,
UpMbps: 100,
DownMbps: 100,
HyObfs: "atresssdaaaadd",
}, &conf.Options{
ListenIP: "127.0.0.1",
HyOptions: conf.HyOptions{},
CertConfig: &conf.CertConfig{
CertFile: "../../test_data/1.pem",
KeyFile: "../../test_data/1.key",
},
}))
s.users.Store("test1111", struct{}{})
go func() {
for {
time.Sleep(10 * time.Second)
auth := base64.StdEncoding.EncodeToString([]byte("test1111"))
log.Println(auth)
log.Println(s.counter.GetUpCount(auth))
}
}()
select {}
}

View File

@@ -1,46 +0,0 @@
package hy
import (
"encoding/base64"
"errors"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/core"
)
func (h *Hy) AddUsers(p *core.AddUsersParams) (int, error) {
s, ok := h.servers.Load(p.Tag)
if !ok {
return 0, errors.New("the node not have")
}
u := &s.(*Server).users
for i := range p.UserInfo {
u.Store(p.UserInfo[i].Uuid, struct{}{})
}
return len(p.UserInfo), nil
}
func (h *Hy) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
v, _ := h.servers.Load(tag)
s := v.(*Server)
auth := base64.StdEncoding.EncodeToString([]byte(uuid))
up = s.counter.GetCounter(auth).UpCounter.Load()
down = s.counter.GetCounter(auth).DownCounter.Load()
if reset {
s.counter.Reset(auth)
}
return
}
func (h *Hy) DelUsers(users []panel.UserInfo, tag string) error {
v, e := h.servers.Load(tag)
if !e {
return errors.New("the node is not have")
}
s := v.(*Server)
for i := range users {
s.users.Delete(users[i].Uuid)
s.counter.Delete(base64.StdEncoding.EncodeToString([]byte(users[i].Uuid)))
}
return nil
}

View File

@@ -1,5 +0,0 @@
//go:build hy
package imports
import _ "github.com/InazumaV/V2bX/core/hy"

View File

@@ -226,6 +226,15 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
in.TrojanOptions.FallbackForALPN = fallbackForALPN
}
}
case "hysteria":
in.Type = "hysteria"
in.HysteriaOptions = option.HysteriaInboundOptions{
ListenOptions: listen,
UpMbps: info.UpMbps,
DownMbps: info.DownMbps,
Obfs: info.HyObfs,
TLS: &tls,
}
}
return in, nil
}

View File

@@ -265,5 +265,7 @@ func (b *Box) Protocols() []string {
return []string{
"v2ray",
"shadowsocks",
"trojan",
"hysteria",
}
}

View File

@@ -59,6 +59,15 @@ func (b *Box) AddUsers(p *core.AddUsersParams) (added int, err error) {
}
}
err = b.inbounds[p.Tag].(*inbound.Trojan).AddUsers(us)
case "hysteria":
us := make([]option.HysteriaUser, len(p.UserInfo))
for i := range p.UserInfo {
us[i] = option.HysteriaUser{
Name: p.UserInfo[i].Uuid,
AuthString: p.UserInfo[i].Uuid,
}
}
err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us)
}
if err != nil {
return 0, err
@@ -91,6 +100,10 @@ func (b *Box) DelUsers(users []panel.UserInfo, tag string) error {
del = i.(*inbound.VMess)
case "shadowsocks":
del = i.(*inbound.ShadowsocksMulti)
case "trojan":
del = i.(*inbound.Trojan)
case "hysteria":
del = i.(*inbound.Hysteria)
}
} else {
return errors.New("the inbound not found")