feat: add Mieru protocol and major system performance optimizations
This commit is contained in:
@@ -2,6 +2,10 @@ package kernel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
"golang.org/x/time/rate"
|
||||
@@ -60,6 +64,21 @@ type Kernel interface {
|
||||
SetSpeedLimitFunc(fn func(uuid string) *rate.Limiter)
|
||||
}
|
||||
|
||||
// ComputeHash returns a hash of config + user identities that would
|
||||
// require a kernel restart/reconstruction if changed.
|
||||
func ComputeHash(nc *panel.NodeConfig, users []panel.User) string {
|
||||
h := sha256.New()
|
||||
configData, _ := json.Marshal(nc)
|
||||
h.Write(configData)
|
||||
sorted := make([]panel.User, len(users))
|
||||
copy(sorted, users)
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i].ID < sorted[j].ID })
|
||||
for _, u := range sorted {
|
||||
fmt.Fprintf(h, "%d:%s,", u.ID, u.UUID)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// Connection represents an active proxy connection reported by the kernel.
|
||||
type Connection struct {
|
||||
ID string
|
||||
|
||||
@@ -351,11 +351,33 @@ func buildInbound(nc *panel.NodeConfig, users []panel.User, certFile, keyFile st
|
||||
return buildSocks(base, users)
|
||||
case "http":
|
||||
return buildHTTP(base, nc, users, certFile, keyFile)
|
||||
case "mieru":
|
||||
return buildMieru(base, nc, users)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildMieru(base M, nc *panel.NodeConfig, users []panel.User) M {
|
||||
base["type"] = "mieru"
|
||||
if nc.Transport != "" {
|
||||
base["transport"] = nc.Transport
|
||||
}
|
||||
if nc.TrafficPattern != "" {
|
||||
base["traffic_pattern"] = nc.TrafficPattern
|
||||
}
|
||||
|
||||
userList := make([]M, 0, len(users))
|
||||
for _, u := range users {
|
||||
userList = append(userList, M{
|
||||
"name": u.UUID,
|
||||
"password": u.UUID,
|
||||
})
|
||||
}
|
||||
base["users"] = userList
|
||||
return base
|
||||
}
|
||||
|
||||
func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M {
|
||||
base["type"] = "shadowsocks"
|
||||
base["method"] = nc.Cipher
|
||||
@@ -394,6 +416,8 @@ func buildVMess(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
applyMultiplex(base, nc)
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
}
|
||||
@@ -418,6 +442,7 @@ func buildVLESS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
applyMultiplex(base, nc)
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
@@ -441,6 +466,8 @@ func buildTrojan(base M, nc *panel.NodeConfig, users []panel.User, certFile, key
|
||||
base["users"] = userList
|
||||
|
||||
applyTransport(base, nc)
|
||||
applyMultiplex(base, nc)
|
||||
|
||||
if nc.TLS == 1 {
|
||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
||||
} else if nc.TLS == 2 {
|
||||
@@ -742,3 +769,31 @@ func buildRealityConfig(nc *panel.NodeConfig) M {
|
||||
tls["reality"] = reality
|
||||
return tls
|
||||
}
|
||||
|
||||
func applyMultiplex(base M, nc *panel.NodeConfig) {
|
||||
if nc.Multiplex == nil || !nc.Multiplex.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
mux := M{
|
||||
"enabled": true,
|
||||
}
|
||||
if nc.Multiplex.Padding {
|
||||
mux["padding"] = true
|
||||
}
|
||||
|
||||
if nc.Multiplex.Brutal != nil && nc.Multiplex.Brutal.Enabled {
|
||||
brutal := M{
|
||||
"enabled": true,
|
||||
}
|
||||
if nc.Multiplex.Brutal.UpMbps > 0 {
|
||||
brutal["up_mbps"] = nc.Multiplex.Brutal.UpMbps
|
||||
}
|
||||
if nc.Multiplex.Brutal.DownMbps > 0 {
|
||||
brutal["down_mbps"] = nc.Multiplex.Brutal.DownMbps
|
||||
}
|
||||
mux["brutal"] = brutal
|
||||
}
|
||||
|
||||
base["multiplex"] = mux
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *SingBox) Name() string { return "sing-box" }
|
||||
func (s *SingBox) Protocols() []string {
|
||||
return []string{
|
||||
"vmess", "vless", "trojan", "shadowsocks",
|
||||
"hysteria2", "tuic", "naive", "socks", "http", "anytls",
|
||||
"hysteria2", "tuic", "naive", "socks", "http", "anytls", "mieru",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,55 +175,61 @@ func (s *SingBox) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certF
|
||||
|
||||
nopFactory := singLog.NewNOPFactory()
|
||||
|
||||
// Configuration hash check for inbound reconstruction
|
||||
configChanged := s.nodeConfig == nil || kernel.ComputeHash(nodeConfig, users) != kernel.ComputeHash(s.nodeConfig, s.users)
|
||||
|
||||
for _, inb := range opts.Inbounds {
|
||||
tag := inb.Tag
|
||||
if existing, ok := im.Get(tag); ok && existing.Type() == inb.Type {
|
||||
var err error
|
||||
switch v := existing.(type) {
|
||||
case adapter.UpdatableInbound[option.VMessUser]:
|
||||
if opts, ok := inb.Options.(*option.VMessInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
if !configChanged {
|
||||
if existing, ok := im.Get(tag); ok && existing.Type() == inb.Type {
|
||||
var err error
|
||||
switch v := existing.(type) {
|
||||
case adapter.UpdatableInbound[option.VMessUser]:
|
||||
if opts, ok := inb.Options.(*option.VMessInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.VLESSUser]:
|
||||
if opts, ok := inb.Options.(*option.VLESSInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.TrojanUser]:
|
||||
if opts, ok := inb.Options.(*option.TrojanInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.Hysteria2User]:
|
||||
if opts, ok := inb.Options.(*option.Hysteria2InboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableShadowsocksInbound:
|
||||
if opts, ok := inb.Options.(*option.ShadowsocksInboundOptions); ok {
|
||||
err = v.UpdateUsersByOptions(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.TUICUser]:
|
||||
if opts, ok := inb.Options.(*option.TUICInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.AnyTLSUser]:
|
||||
if opts, ok := inb.Options.(*option.AnyTLSInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[auth.User]:
|
||||
switch opts := inb.Options.(type) {
|
||||
case *option.NaiveInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
case *option.SocksInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
case *option.HTTPMixedInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
}
|
||||
case adapter.UpdatableInbound[option.VLESSUser]:
|
||||
if opts, ok := inb.Options.(*option.VLESSInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.TrojanUser]:
|
||||
if opts, ok := inb.Options.(*option.TrojanInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.Hysteria2User]:
|
||||
if opts, ok := inb.Options.(*option.Hysteria2InboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableShadowsocksInbound:
|
||||
if opts, ok := inb.Options.(*option.ShadowsocksInboundOptions); ok {
|
||||
err = v.UpdateUsersByOptions(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.TUICUser]:
|
||||
if opts, ok := inb.Options.(*option.TUICInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[option.AnyTLSUser]:
|
||||
if opts, ok := inb.Options.(*option.AnyTLSInboundOptions); ok {
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
}
|
||||
case adapter.UpdatableInbound[auth.User]:
|
||||
switch opts := inb.Options.(type) {
|
||||
case *option.NaiveInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
case *option.SocksInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
case *option.HTTPMixedInboundOptions:
|
||||
err = v.UpdateUsers(opts.Users)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
slog.Warn("incremental update failed, falling back to recreate", "tag", tag, "error", err)
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
slog.Warn("incremental update failed, falling back to recreate", "tag", tag, "error", err)
|
||||
}
|
||||
|
||||
// Config mismatch or incremental update failure: Reconstruct Inbound.
|
||||
// Remove the existing inbound first so im.Create() can bind the same port.
|
||||
// im.Create() cannot atomically swap a TCP listener — it tries to start the
|
||||
// new socket before the old one is closed, causing "address already in use".
|
||||
|
||||
@@ -460,6 +460,16 @@ func applyStreamSettings(base M, nc *panel.NodeConfig, certFile, keyFile string)
|
||||
ss["realitySettings"] = buildRealitySettings(nc)
|
||||
}
|
||||
|
||||
// Proxy Protocol
|
||||
if nc.AcceptProxyProtocol {
|
||||
sockopt, ok := base["streamSettings"].(M)["sockopt"].(M)
|
||||
if !ok {
|
||||
sockopt = M{}
|
||||
}
|
||||
sockopt["acceptProxyProtocol"] = true
|
||||
ss["sockopt"] = sockopt
|
||||
}
|
||||
|
||||
base["streamSettings"] = ss
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -152,7 +151,7 @@ func (x *Xray) Start(nodeConfig *panel.NodeConfig, users []panel.User, certFile,
|
||||
x.protocol = nodeConfig.Protocol
|
||||
x.inboundTag = nodeConfig.Protocol + "-in"
|
||||
x.cumTraffic = make(map[int][2]int64)
|
||||
x.lastKernelHash = computeKernelHash(nodeConfig, users)
|
||||
x.lastKernelHash = kernel.ComputeHash(nodeConfig, users)
|
||||
x.running.Store(true)
|
||||
x.mu.Unlock()
|
||||
|
||||
@@ -165,23 +164,26 @@ func (x *Xray) Start(nodeConfig *panel.NodeConfig, users []panel.User, certFile,
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload hot-swaps dispatcher limits and, only when kernel-affecting fields
|
||||
// have changed (user identities, routes, protocol), triggers a full restart.
|
||||
// Reload updates dispatcher limits and handles configuration changes.
|
||||
// Since Xray does not support granular inbound reconstruction without an
|
||||
// instance restart for most transport/TLS settings, it triggers a full restart
|
||||
// if any kernel-affecting fields (hash mismatch) have changed.
|
||||
func (x *Xray) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certFile, keyFile string) error {
|
||||
x.updateDispatcherLimits(users)
|
||||
|
||||
newHash := computeKernelHash(nodeConfig, users)
|
||||
newHash := kernel.ComputeHash(nodeConfig, users)
|
||||
|
||||
x.mu.Lock()
|
||||
same := x.lastKernelHash == newHash
|
||||
if same {
|
||||
x.users = users
|
||||
x.mu.Unlock()
|
||||
slog.Info("xray: limits updated, kernel unchanged")
|
||||
slog.Debug("xray: limits updated, kernel configuration unchanged")
|
||||
return nil
|
||||
}
|
||||
x.mu.Unlock()
|
||||
|
||||
slog.Info("xray: configuration changed, performing full restart")
|
||||
return x.Start(nodeConfig, users, certFile, keyFile)
|
||||
}
|
||||
|
||||
@@ -745,18 +747,3 @@ func (x *Xray) updateDispatcherLimits(users []panel.User) {
|
||||
// so that concurrent Xray instances in multi-node mode each capture their
|
||||
// own LimitDispatcher.
|
||||
var xrayCreationMu sync.Mutex
|
||||
|
||||
// computeKernelHash returns a hash of config + user identities that would
|
||||
// require a kernel restart if changed. Limit-only changes are excluded.
|
||||
func computeKernelHash(nc *panel.NodeConfig, users []panel.User) string {
|
||||
h := sha256.New()
|
||||
configData, _ := json.Marshal(nc)
|
||||
h.Write(configData)
|
||||
sorted := make([]panel.User, len(users))
|
||||
copy(sorted, users)
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i].ID < sorted[j].ID })
|
||||
for _, u := range sorted {
|
||||
fmt.Fprintf(h, "%d:%s,", u.ID, u.UUID)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user