mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 12:40:11 +00:00
add singbox core(just started)
This commit is contained in:
@@ -1,68 +0,0 @@
|
||||
package hy
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type UserTrafficCounter struct {
|
||||
counters map[string]*counters
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type counters struct {
|
||||
UpCounter atomic.Int64
|
||||
DownCounter atomic.Int64
|
||||
//ConnGauge atomic.Int64
|
||||
}
|
||||
|
||||
func NewUserTrafficCounter() *UserTrafficCounter {
|
||||
return &UserTrafficCounter{
|
||||
counters: map[string]*counters{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) getCounters(auth string) *counters {
|
||||
c.lock.RLock()
|
||||
cts, ok := c.counters[auth]
|
||||
c.lock.RUnlock()
|
||||
if !ok {
|
||||
cts = &counters{}
|
||||
c.counters[auth] = cts
|
||||
}
|
||||
return cts
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) Rx(auth string, n int) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.DownCounter.Add(int64(n))
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) Tx(auth string, n int) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.UpCounter.Add(int64(n))
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) IncConn(_ string) {
|
||||
/*cts := c.getCounters(auth)
|
||||
cts.ConnGauge.Add(1)*/
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) DecConn(_ string) {
|
||||
/*cts := c.getCounters(auth)
|
||||
cts.ConnGauge.Add(1)*/
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) Reset(auth string) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.UpCounter.Store(0)
|
||||
cts.DownCounter.Store(0)
|
||||
}
|
||||
|
||||
func (c *UserTrafficCounter) Delete(auth string) {
|
||||
c.lock.Lock()
|
||||
delete(c.counters, auth)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package hy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUserTrafficCounter_Rx(t *testing.T) {
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package hy
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/limiter"
|
||||
|
||||
@@ -3,11 +3,12 @@ package hy
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/Yuzuki616/hysteria/core/utils"
|
||||
rdns "github.com/folbricht/routedns"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Yuzuki616/hysteria/core/utils"
|
||||
rdns "github.com/folbricht/routedns"
|
||||
)
|
||||
|
||||
var errInvalidSyntax = errors.New("invalid syntax")
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/counter"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/limiter"
|
||||
@@ -34,7 +36,7 @@ var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFac
|
||||
type Server struct {
|
||||
tag string
|
||||
l *limiter.Limiter
|
||||
counter *UserTrafficCounter
|
||||
counter *counter.TrafficCounter
|
||||
users sync.Map
|
||||
running atomic.Bool
|
||||
*cs.Server
|
||||
@@ -122,7 +124,7 @@ func (s *Server) runServer(node *panel.NodeInfo, c *conf.ControllerConfig) error
|
||||
// ACL
|
||||
var aclEngine *acl.Engine
|
||||
// Prometheus
|
||||
s.counter = NewUserTrafficCounter()
|
||||
s.counter = counter.NewTrafficCounter()
|
||||
// Packet conn
|
||||
pktConnFuncFactory := serverPacketConnFuncFactoryMap[""]
|
||||
if pktConnFuncFactory == nil {
|
||||
|
||||
@@ -2,13 +2,14 @@ package hy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/limiter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package hy
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/core"
|
||||
)
|
||||
@@ -23,8 +24,8 @@ 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.getCounters(auth).UpCounter.Load()
|
||||
down = s.counter.getCounters(auth).DownCounter.Load()
|
||||
up = s.counter.GetCounter(auth).UpCounter.Load()
|
||||
down = s.counter.GetCounter(auth).DownCounter.Load()
|
||||
if reset {
|
||||
s.counter.Reset(auth)
|
||||
}
|
||||
|
||||
5
core/imports/sing.go
Normal file
5
core/imports/sing.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build sing
|
||||
|
||||
package imports
|
||||
|
||||
import _ "github.com/Yuzuki616/V2bX/core/sing"
|
||||
79
core/sing/box_outbound.go
Normal file
79
core/sing/box_outbound.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/inazumav/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func (b *Box) startOutbounds() error {
|
||||
outboundTags := make(map[adapter.Outbound]string)
|
||||
outbounds := make(map[string]adapter.Outbound)
|
||||
for i, outboundToStart := range b.outbounds {
|
||||
var outboundTag string
|
||||
if outboundToStart.Tag() == "" {
|
||||
outboundTag = F.ToString(i)
|
||||
} else {
|
||||
outboundTag = outboundToStart.Tag()
|
||||
}
|
||||
if _, exists := outbounds[outboundTag]; exists {
|
||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
||||
}
|
||||
outboundTags[outboundToStart] = outboundTag
|
||||
outbounds[outboundTag] = outboundToStart
|
||||
}
|
||||
started := make(map[string]bool)
|
||||
for {
|
||||
canContinue := false
|
||||
startOne:
|
||||
for _, outboundToStart := range b.outbounds {
|
||||
outboundTag := outboundTags[outboundToStart]
|
||||
if started[outboundTag] {
|
||||
continue
|
||||
}
|
||||
dependencies := outboundToStart.Dependencies()
|
||||
for _, dependency := range dependencies {
|
||||
if !started[dependency] {
|
||||
continue startOne
|
||||
}
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
|
||||
b.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(started) == len(b.outbounds) {
|
||||
break
|
||||
}
|
||||
if canContinue {
|
||||
continue
|
||||
}
|
||||
currentOutbound := common.Find(b.outbounds, func(it adapter.Outbound) bool {
|
||||
return !started[outboundTags[it]]
|
||||
})
|
||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||
return !started[it]
|
||||
})
|
||||
if common.Contains(oTree, problemOutboundTag) {
|
||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||
}
|
||||
problemOutbound := outbounds[problemOutboundTag]
|
||||
if problemOutbound == nil {
|
||||
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||
}
|
||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||
}
|
||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
core/sing/debug_go118.go
Normal file
36
core/sing/debug_go118.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build !go1.19
|
||||
|
||||
package sing
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/inazumav/sing-box/common/dialer/conntrack"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
applyDebugListenOption(options)
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
36
core/sing/debug_go119.go
Normal file
36
core/sing/debug_go119.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build go1.19
|
||||
|
||||
package sing
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/inazumav/sing-box/common/dialer/conntrack"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
applyDebugListenOption(options)
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
67
core/sing/debug_http.go
Normal file
67
core/sing/debug_http.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/inazumav/sing-box/common/badjson"
|
||||
"github.com/inazumav/sing-box/common/json"
|
||||
"github.com/inazumav/sing-box/log"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var debugHTTPServer *http.Server
|
||||
|
||||
func applyDebugListenOption(options option.DebugOptions) {
|
||||
if debugHTTPServer != nil {
|
||||
debugHTTPServer.Close()
|
||||
debugHTTPServer = nil
|
||||
}
|
||||
if options.Listen == "" {
|
||||
return
|
||||
}
|
||||
r := chi.NewMux()
|
||||
r.Route("/debug", func(r chi.Router) {
|
||||
r.Get("/gc", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
go debug.FreeOSMemory()
|
||||
})
|
||||
r.Get("/memory", func(writer http.ResponseWriter, request *http.Request) {
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(memObject)
|
||||
})
|
||||
r.HandleFunc("/pprof", pprof.Index)
|
||||
r.HandleFunc("/pprof/*", pprof.Index)
|
||||
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/pprof/profile", pprof.Profile)
|
||||
r.HandleFunc("/pprof/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/pprof/trace", pprof.Trace)
|
||||
})
|
||||
debugHTTPServer = &http.Server{
|
||||
Addr: options.Listen,
|
||||
Handler: r,
|
||||
}
|
||||
go func() {
|
||||
err := debugHTTPServer.ListenAndServe()
|
||||
if err != nil && !E.IsClosed(err) {
|
||||
log.Error(E.Cause(err, "serve debug HTTP server"))
|
||||
}
|
||||
}()
|
||||
}
|
||||
23
core/sing/debug_linux.go
Normal file
23
core/sing/debug_linux.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func rusageMaxRSS() float64 {
|
||||
ru := syscall.Rusage{}
|
||||
err := syscall.Getrusage(syscall.RUSAGE_SELF, &ru)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
rss := float64(ru.Maxrss)
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
||||
rss /= 1 << 20 // ru_maxrss is bytes on darwin
|
||||
} else {
|
||||
// ru_maxrss is kilobytes elsewhere (linux, openbsd, etc)
|
||||
rss /= 1 << 10
|
||||
}
|
||||
return rss
|
||||
}
|
||||
7
core/sing/debug_stub.go
Normal file
7
core/sing/debug_stub.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !linux
|
||||
|
||||
package sing
|
||||
|
||||
func rusageMaxRSS() float64 {
|
||||
return -1
|
||||
}
|
||||
58
core/sing/hook.go
Normal file
58
core/sing/hook.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/counter"
|
||||
"github.com/inazumav/sing-box/adapter"
|
||||
"github.com/inazumav/sing-box/log"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type HookServer struct {
|
||||
hooker *Hooker
|
||||
}
|
||||
|
||||
func NewHookServer(logger log.Logger) *HookServer {
|
||||
return &HookServer{
|
||||
hooker: &Hooker{
|
||||
logger: logger,
|
||||
counter: make(map[string]*counter.TrafficCounter),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HookServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookServer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookServer) StatsService() adapter.V2RayStatsService {
|
||||
return h.hooker
|
||||
}
|
||||
|
||||
func (h *HookServer) Hooker() *Hooker {
|
||||
return h.hooker
|
||||
}
|
||||
|
||||
type Hooker struct {
|
||||
logger log.Logger
|
||||
counter map[string]*counter.TrafficCounter
|
||||
}
|
||||
|
||||
func (h *Hooker) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn {
|
||||
if c, ok := h.counter[inbound]; ok {
|
||||
return counter.NewConnCounter(conn, c.GetCounter(user))
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (h *Hooker) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
||||
if c, ok := h.counter[inbound]; ok {
|
||||
return counter.NewPacketConnCounter(conn, c.GetCounter(user))
|
||||
}
|
||||
return conn
|
||||
}
|
||||
133
core/sing/node.go
Normal file
133
core/sing/node.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/inazumav/sing-box/inbound"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
)
|
||||
|
||||
type WsNetworkConfig struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.ControllerConfig) (option.Inbound, error) {
|
||||
addr, _ := netip.ParseAddr("0.0.0.0")
|
||||
listen := option.ListenOptions{
|
||||
Listen: (*option.ListenAddress)(&addr),
|
||||
ListenPort: uint16(info.Port),
|
||||
}
|
||||
tls := option.InboundTLSOptions{
|
||||
Enabled: info.Tls,
|
||||
CertificatePath: c.CertConfig.CertFile,
|
||||
KeyPath: c.CertConfig.KeyFile,
|
||||
ServerName: info.ServerName,
|
||||
}
|
||||
in := option.Inbound{
|
||||
Tag: tag,
|
||||
}
|
||||
switch info.Type {
|
||||
case "v2ray":
|
||||
in.Type = "vmess"
|
||||
t := option.V2RayTransportOptions{
|
||||
Type: info.Network,
|
||||
}
|
||||
switch info.Network {
|
||||
case "tcp":
|
||||
case "ws":
|
||||
network := WsNetworkConfig{}
|
||||
err := json.Unmarshal(info.NetworkSettings, &network)
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
|
||||
}
|
||||
var u *url.URL
|
||||
u, err = url.Parse(network.Path)
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("parse path error: %s", err)
|
||||
}
|
||||
ed, _ := strconv.Atoi(u.Query().Get("ed"))
|
||||
t.WebsocketOptions = option.V2RayWebsocketOptions{
|
||||
Path: u.Path,
|
||||
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
||||
MaxEarlyData: uint32(ed),
|
||||
}
|
||||
case "grpc":
|
||||
t.GRPCOptions = option.V2RayGRPCOptions{
|
||||
ServiceName: info.ServerName,
|
||||
}
|
||||
}
|
||||
in.VMessOptions = option.VMessInboundOptions{
|
||||
ListenOptions: listen,
|
||||
TLS: &tls,
|
||||
Transport: &t,
|
||||
}
|
||||
case "shadowsocks":
|
||||
in.Type = "shadowsocks"
|
||||
p := make([]byte, 32)
|
||||
_, _ = rand.Read(p)
|
||||
randomPasswd := hex.EncodeToString(p)
|
||||
if strings.Contains(info.Cipher, "2022") {
|
||||
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
|
||||
}
|
||||
in.ShadowsocksOptions = option.ShadowsocksInboundOptions{
|
||||
ListenOptions: listen,
|
||||
Method: info.Cipher,
|
||||
Password: info.ServerKey,
|
||||
Users: []option.ShadowsocksUser{
|
||||
{
|
||||
Password: randomPasswd,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (b *Box) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error {
|
||||
c, err := getInboundOptions(tag, info, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err := inbound.New(
|
||||
context.Background(),
|
||||
b.router,
|
||||
b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")),
|
||||
c,
|
||||
nil,
|
||||
)
|
||||
b.inbounds[tag] = in
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("start inbound error: %s", err)
|
||||
}
|
||||
err = b.router.AddInbound(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add inbound error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Box) DelNode(tag string) error {
|
||||
err := b.inbounds[tag].Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("close inbound error: %s", err)
|
||||
}
|
||||
err = b.router.DelInbound(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete inbound error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
263
core/sing/sing.go
Normal file
263
core/sing/sing.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
vCore "github.com/Yuzuki616/V2bX/core"
|
||||
|
||||
"github.com/inazumav/sing-box/adapter"
|
||||
"github.com/inazumav/sing-box/inbound"
|
||||
"github.com/inazumav/sing-box/log"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
"github.com/inazumav/sing-box/outbound"
|
||||
"github.com/inazumav/sing-box/route"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ adapter.Service = (*Box)(nil)
|
||||
|
||||
type Box struct {
|
||||
createdAt time.Time
|
||||
router adapter.Router
|
||||
inbounds map[string]adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
hookServer *HookServer
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
vCore.RegisterCore("sing", New)
|
||||
}
|
||||
|
||||
func New(_ *conf.CoreConfig) (vCore.Core, error) {
|
||||
options := option.Options{}
|
||||
ctx := context.Background()
|
||||
createdAt := time.Now()
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||
var defaultLogWriter io.Writer
|
||||
logFactory, err := log.New(log.Options{
|
||||
Context: ctx,
|
||||
Options: common.PtrValueOrDefault(options.Log),
|
||||
DefaultWriter: defaultLogWriter,
|
||||
BaseTime: createdAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create log factory")
|
||||
}
|
||||
router, err := route.NewRouter(
|
||||
ctx,
|
||||
logFactory,
|
||||
common.PtrValueOrDefault(options.Route),
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
}
|
||||
inbounds := make([]adapter.Inbound, len(options.Inbounds))
|
||||
inMap := make(map[string]adapter.Inbound, len(inbounds))
|
||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
var in adapter.Inbound
|
||||
var tag string
|
||||
if inboundOptions.Tag != "" {
|
||||
tag = inboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
in, err = inbound.New(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
inboundOptions,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
}
|
||||
inbounds[i] = in
|
||||
inMap[inboundOptions.Tag] = in
|
||||
}
|
||||
for i, outboundOptions := range options.Outbounds {
|
||||
var out adapter.Outbound
|
||||
var tag string
|
||||
if outboundOptions.Tag != "" {
|
||||
tag = outboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
out, err = outbound.New(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
outboundOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||
}
|
||||
outbounds = append(outbounds, out)
|
||||
}
|
||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
||||
common.Must(oErr)
|
||||
outbounds = append(outbounds, out)
|
||||
return out
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := NewHookServer(logFactory.NewLogger("Hook-Server"))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create v2ray api server")
|
||||
}
|
||||
router.SetV2RayServer(server)
|
||||
return &Box{
|
||||
router: router,
|
||||
inbounds: inMap,
|
||||
outbounds: outbounds,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
hookServer: server,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Box) PreStart() error {
|
||||
err := b.preStart()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
b.Close()
|
||||
return err
|
||||
}
|
||||
b.logger.Info("sing-box pre-started (", F.Seconds(time.Since(b.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Box) Start() error {
|
||||
err := b.start()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
b.Close()
|
||||
return err
|
||||
}
|
||||
b.logger.Info("sing-box started (", F.Seconds(time.Since(b.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Box) preStart() error {
|
||||
err := b.startOutbounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.router.Start()
|
||||
}
|
||||
|
||||
func (b *Box) start() error {
|
||||
err := b.preStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, in := range b.inbounds {
|
||||
var tag string
|
||||
if in.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = in.Tag()
|
||||
}
|
||||
b.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Box) postStart() error {
|
||||
for serviceName, service := range b.outbounds {
|
||||
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
|
||||
b.logger.Trace("post-starting ", service)
|
||||
err := lateService.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start ", serviceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Box) Close() error {
|
||||
select {
|
||||
case <-b.done:
|
||||
return os.ErrClosed
|
||||
default:
|
||||
close(b.done)
|
||||
}
|
||||
var errors error
|
||||
for i, in := range b.inbounds {
|
||||
b.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
for i, out := range b.outbounds {
|
||||
b.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
b.logger.Trace("closing router")
|
||||
if err := common.Close(b.router); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close router")
|
||||
})
|
||||
}
|
||||
b.logger.Trace("closing log factory")
|
||||
if err := common.Close(b.logFactory); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close log factory")
|
||||
})
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
func (b *Box) Router() adapter.Router {
|
||||
return b.router
|
||||
}
|
||||
|
||||
func (b *Box) Protocols() []string {
|
||||
return []string{
|
||||
"v2ray",
|
||||
"shadowsocks",
|
||||
}
|
||||
}
|
||||
75
core/sing/user.go
Normal file
75
core/sing/user.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/core"
|
||||
"github.com/inazumav/sing-box/inbound"
|
||||
"github.com/inazumav/sing-box/option"
|
||||
)
|
||||
|
||||
func (b *Box) AddUsers(p *core.AddUsersParams) (added int, err error) {
|
||||
switch p.NodeInfo.Type {
|
||||
case "v2ray":
|
||||
us := make([]option.VMessUser, len(p.UserInfo))
|
||||
for i := range p.UserInfo {
|
||||
us[i] = option.VMessUser{
|
||||
Name: p.UserInfo[i].Uuid,
|
||||
UUID: p.UserInfo[i].Uuid,
|
||||
}
|
||||
}
|
||||
err = b.inbounds[p.Tag].(*inbound.VMess).AddUsers(us)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "shadowsocks":
|
||||
us := make([]option.ShadowsocksUser, len(p.UserInfo))
|
||||
for i := range p.UserInfo {
|
||||
us[i] = option.ShadowsocksUser{
|
||||
Name: p.UserInfo[i].Uuid,
|
||||
Password: p.UserInfo[i].Uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(p.UserInfo), err
|
||||
}
|
||||
|
||||
func (b *Box) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
|
||||
if c, ok := b.hookServer.Hooker().counter[tag]; ok {
|
||||
up = c.GetUpCount(uuid)
|
||||
down = c.GetDownCount(uuid)
|
||||
if reset {
|
||||
c.Reset(uuid)
|
||||
}
|
||||
return
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
type UserDeleter interface {
|
||||
DelUsers(uuid []string) error
|
||||
}
|
||||
|
||||
func (b *Box) DelUsers(users []panel.UserInfo, tag string) error {
|
||||
var del UserDeleter
|
||||
if i, ok := b.inbounds[tag]; ok {
|
||||
switch i.Type() {
|
||||
case "vmess":
|
||||
del = i.(*inbound.VMess)
|
||||
case "shadowsocks":
|
||||
del = i.(*inbound.ShadowsocksMulti)
|
||||
}
|
||||
} else {
|
||||
return errors.New("the inbound not found")
|
||||
}
|
||||
uuids := make([]string, len(users))
|
||||
for i := range users {
|
||||
uuids[i] = users[i].Uuid
|
||||
}
|
||||
err := del.DelUsers(uuids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -7,10 +7,11 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -70,5 +70,5 @@ import (
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/tls"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/utp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/wechat"
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
|
||||
//_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package xray
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/xtls/xray-core/core"
|
||||
|
||||
@@ -2,6 +2,7 @@ package xray
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
conf2 "github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
|
||||
@@ -2,13 +2,14 @@ package xray
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/common/format"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func buildSSUsers(tag string, userInfo []panel.UserInfo, cypher string, serverKey string) (users []*protocol.User) {
|
||||
|
||||
Reference in New Issue
Block a user