Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP

This commit is contained in:
Sergei Maklagin
2026-05-11 00:59:35 +03:00
parent 652e0baf57
commit 3bd162ed6f
241 changed files with 36409 additions and 4086 deletions

View File

@@ -9,6 +9,14 @@ type Squad struct {
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
type DeletedSquad struct {
Squad Squad
OrphanedNodeUUIDs []string
OrphanedConnectionLimiterIDs []int
OrphanedTrafficLimiterIDs []int
SurvivingNodeUUIDs []string
}
type SquadCreate struct {
Name string `json:"name" validate:"required"`
}
@@ -20,7 +28,7 @@ type SquadUpdate struct {
type Node struct {
UUID string `json:"uuid" validate:"required,uuid4"`
Name string `json:"name" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
@@ -28,7 +36,7 @@ type Node struct {
type NodeCreate struct {
UUID string `json:"uuid" validate:"required,uuid4"`
Name string `json:"name" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
}
type NodeUpdate struct {
@@ -42,10 +50,10 @@ type BaseNode struct {
type User struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Type string `json:"type" validate:"required"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required"`
UUID string `json:"uuid" validate:"required"`
Password string `json:"password" validate:"required"`
Secret string `json:"secret" validate:"required"`
@@ -56,10 +64,10 @@ type User struct {
}
type UserCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"required"`
Type string `json:"type" validate:"required,oneof=hysteria hysteria2 mtproxy trojan tuic vless vmess"`
Inbound string `json:"inbound" validate:"required"`
Type string `json:"type" validate:"required,oneof=hysteria hysteria2 mtproxy trojan tuic vless vmess"`
UUID string `json:"uuid" validate:"omitempty,uuid4"`
Password string `json:"password" validate:"omitempty"`
Secret string `json:"secret" validate:"omitempty"`
@@ -85,53 +93,54 @@ type BaseUser struct {
type ConnectionLimiter struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required"`
Username string `json:"username" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection"`
ConnectionType string `json:"connection_type" validate:"omitempty,oneof=hwid mux ip"`
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
Strategy string `json:"strategy" validate:"required,oneof=connection bypass"`
ConnectionType string `json:"connection_type" validate:"omitempty,oneof=default hwid mux ip"`
LockType string `json:"lock_type" validate:"required,oneof=manager default"`
Count uint32 `json:"count" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
type ConnectionLimiterCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required"`
Username string `json:"username" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection"`
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
Count uint32 `json:"count" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty,oneof=default hwid mux ip"`
LockType string `json:"lock_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=manager default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type ConnectionLimiterUpdate struct {
Username string `json:"username" validate:"required"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection"`
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
Count uint32 `json:"count" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty,oneof=default hwid mux ip"`
LockType string `json:"lock_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=manager default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BaseConnectionLimiter struct {
Username string `json:"username" validate:"required"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection"`
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
Count uint32 `json:"count" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty,oneof=default hwid mux ip"`
LockType string `json:"lock_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=manager default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BandwidthLimiter struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required"`
Username string `json:"username" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required"`
Mode string `json:"mode" validate:"required"`
ConnectionType string `json:"connection_type" validate:"omitempty"`
Mode string `json:"mode" validate:"required"`
FlowKeys []string `json:"flow_keys" validate:"omitempty,dive,oneof=user destination ip hwid mux"`
Speed string `json:"speed" validate:"required"`
RawSpeed uint64 `json:"raw_speed" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
@@ -139,30 +148,116 @@ type BandwidthLimiter struct {
}
type BandwidthLimiterCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required"`
Username string `json:"username" validate:"required"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
Mode string `json:"mode" validate:"required"`
ConnectionType string `json:"connection_type" validate:"omitempty"`
Speed string `json:"speed" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
FlowKeys []string `json:"flow_keys" validate:"excluded_if=Strategy bypass,omitempty,dive,oneof=user destination ip hwid mux"`
Speed string `json:"speed" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BandwidthLimiterUpdate struct {
Username string `json:"username" validate:"required"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
Mode string `json:"mode" validate:"required"`
ConnectionType string `json:"connection_type" validate:"omitempty"`
Speed string `json:"speed" validate:"required"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
FlowKeys []string `json:"flow_keys" validate:"excluded_if=Strategy bypass,omitempty,dive,oneof=user destination ip hwid mux"`
Speed string `json:"speed" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BaseBandwidthLimiter struct {
Username string `json:"username" validate:"required"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
Mode string `json:"mode" validate:"required"`
ConnectionType string `json:"connection_type" validate:"omitempty"`
Speed string `json:"speed" validate:"required"`
RawSpeed uint64 `json:"raw_speed" validate:"required"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global connection bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,omitempty"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
FlowKeys []string `json:"flow_keys" validate:"excluded_if=Strategy bypass,omitempty,dive,oneof=user destination ip hwid mux"`
Speed string `json:"speed" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
RawSpeed uint64 `json:"raw_speed" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type TrafficLimiter struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global bypass"`
Mode string `json:"mode" validate:"required"`
RawUsed uint64 `json:"raw_used" validate:"required"`
Quota string `json:"quota" validate:"required"`
RawQuota uint64 `json:"raw_quota" validate:"required"`
Usage uint8 `json:"usage"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
type TrafficLimiterCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global bypass"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Quota string `json:"quota" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type TrafficLimiterUpdate struct {
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global bypass"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Quota string `json:"quota" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BaseTrafficLimiter struct {
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=global bypass"`
Mode string `json:"mode" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
RawUsed uint64 `json:"raw_used" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Quota string `json:"quota" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
RawQuota uint64 `json:"raw_quota" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type RateLimiter struct {
ID int `json:"id" validate:"required"`
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=fixed_window sliding_window token_bucket leaky_bucket bypass"`
ConnectionType string `json:"connection_type" validate:"required,oneof=hwid mux ip default"`
Count uint32 `json:"count" validate:"required"`
Interval string `json:"interval" validate:"required"`
CreatedAt time.Time `json:"created_at" validate:"required"`
UpdatedAt time.Time `json:"updated_at" validate:"required"`
}
type RateLimiterCreate struct {
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=fixed_window sliding_window token_bucket leaky_bucket bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=hwid mux ip default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Interval string `json:"interval" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type RateLimiterUpdate struct {
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=fixed_window sliding_window token_bucket leaky_bucket bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=hwid mux ip default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Interval string `json:"interval" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}
type BaseRateLimiter struct {
Username string `json:"username" validate:"omitempty"`
Outbound string `json:"outbound" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=fixed_window sliding_window token_bucket leaky_bucket bypass"`
ConnectionType string `json:"connection_type" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass,omitempty,oneof=hwid mux ip default"`
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
Interval string `json:"interval" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
}

View File

@@ -1,15 +1,6 @@
package constant
type NodeManager interface {
AddNode(id string, node ConnectedNode) error
AcquireLock(limiterId int, id string) (string, error)
RefreshLock(limiterId int, id string, handleId string) error
ReleaseLock(limiterId int, id string, handleId string) error
}
type Manager interface {
NodeManager
CreateSquad(user SquadCreate) (Squad, error)
GetSquads(filters map[string][]string) ([]Squad, error)
GetSquadsCount(filters map[string][]string) (int, error)
@@ -21,7 +12,7 @@ type Manager interface {
GetNodes(filters map[string][]string) ([]Node, error)
GetNodesCount(filters map[string][]string) (int, error)
GetNode(uuid string) (Node, error)
GetNodeStatus(uuid string) string
GetNodeStatus(uuid string) (string, error)
UpdateNode(uuid string, node NodeUpdate) (Node, error)
DeleteNode(uuid string) (Node, error)
@@ -39,10 +30,35 @@ type Manager interface {
UpdateBandwidthLimiter(id int, limiter BandwidthLimiterUpdate) (BandwidthLimiter, error)
DeleteBandwidthLimiter(id int) (BandwidthLimiter, error)
CreateTrafficLimiter(limiter TrafficLimiterCreate) (TrafficLimiter, error)
GetTrafficLimiters(filters map[string][]string) ([]TrafficLimiter, error)
GetTrafficLimitersCount(filters map[string][]string) (int, error)
GetTrafficLimiter(id int) (TrafficLimiter, error)
UpdateTrafficLimiter(id int, limiter TrafficLimiterUpdate) (TrafficLimiter, error)
UpdateTrafficLimiterUsed(id int, used uint64) (TrafficLimiter, error)
DeleteTrafficLimiter(id int) (TrafficLimiter, error)
CreateConnectionLimiter(limiter ConnectionLimiterCreate) (ConnectionLimiter, error)
GetConnectionLimiters(filters map[string][]string) ([]ConnectionLimiter, error)
GetConnectionLimitersCount(filters map[string][]string) (int, error)
GetConnectionLimiter(id int) (ConnectionLimiter, error)
UpdateConnectionLimiter(id int, limiter ConnectionLimiterUpdate) (ConnectionLimiter, error)
DeleteConnectionLimiter(id int) (ConnectionLimiter, error)
CreateRateLimiter(limiter RateLimiterCreate) (RateLimiter, error)
GetRateLimiters(filters map[string][]string) ([]RateLimiter, error)
GetRateLimitersCount(filters map[string][]string) (int, error)
GetRateLimiter(id int) (RateLimiter, error)
UpdateRateLimiter(id int, limiter RateLimiterUpdate) (RateLimiter, error)
DeleteRateLimiter(id int) (RateLimiter, error)
}
type NodeManager interface {
AddNode(id string, node ConnectedNode) error
AcquireLock(limiterId int, id string) (string, error)
RefreshLock(limiterId int, id string, handleId string) error
ReleaseLock(limiterId int, id string, handleId string) error
AddTrafficUsage(limiterId int, n uint64) (uint64, error)
}

View File

@@ -13,6 +13,14 @@ type ConnectedNode interface {
UpdateBandwidthLimiters(limiter []BandwidthLimiter)
DeleteBandwidthLimiter(limiter BandwidthLimiter)
UpdateTrafficLimiter(limiter TrafficLimiter)
UpdateTrafficLimiters(limiter []TrafficLimiter)
DeleteTrafficLimiter(limiter TrafficLimiter)
UpdateRateLimiter(limiter RateLimiter)
UpdateRateLimiters(limiter []RateLimiter)
DeleteRateLimiter(limiter RateLimiter)
IsLocal() bool
IsOnline() bool

View File

@@ -6,7 +6,7 @@ type Repository interface {
GetSquadsCount(filters map[string][]string) (int, error)
GetSquad(id int) (Squad, error)
UpdateSquad(id int, user SquadUpdate) (Squad, error)
DeleteSquad(id int) (Squad, error)
DeleteSquad(id int) (DeletedSquad, error)
CreateNode(node NodeCreate) (Node, error)
GetNodes(filters map[string][]string) ([]Node, error)
@@ -35,4 +35,19 @@ type Repository interface {
GetBandwidthLimiter(id int) (BandwidthLimiter, error)
UpdateBandwidthLimiter(id int, limiter BandwidthLimiterUpdate) (BandwidthLimiter, error)
DeleteBandwidthLimiter(id int) (BandwidthLimiter, error)
CreateTrafficLimiter(limiter TrafficLimiterCreate) (TrafficLimiter, error)
GetTrafficLimiters(filters map[string][]string) ([]TrafficLimiter, error)
GetTrafficLimitersCount(filters map[string][]string) (int, error)
GetTrafficLimiter(id int) (TrafficLimiter, error)
UpdateTrafficLimiter(id int, limiter TrafficLimiterUpdate) (TrafficLimiter, error)
UpdateTrafficLimiterUsed(id int, used uint64) (TrafficLimiter, error)
DeleteTrafficLimiter(id int) (TrafficLimiter, error)
CreateRateLimiter(limiter RateLimiterCreate) (RateLimiter, error)
GetRateLimiters(filters map[string][]string) ([]RateLimiter, error)
GetRateLimitersCount(filters map[string][]string) (int, error)
GetRateLimiter(id int) (RateLimiter, error)
UpdateRateLimiter(id int, limiter RateLimiterUpdate) (RateLimiter, error)
DeleteRateLimiter(id int) (RateLimiter, error)
}

View File

@@ -5,7 +5,7 @@ import (
"strconv"
"github.com/huandu/go-sqlbuilder"
"github.com/sagernet/sing/common/byteformats"
"github.com/sagernet/sing-box/common/byteformats"
)
type Filter func(sb *sqlbuilder.SelectBuilder, value []string) error
@@ -96,6 +96,20 @@ func ExistsAndWhereInFilter(subquery *sqlbuilder.SelectBuilder, field string) Fi
}
}
func InFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
if len(value) == 0 {
return nil
}
values := make([]interface{}, len(value))
for i, v := range value {
values[i] = v
}
sb.Where(sb.In(field, values...))
return nil
}
}
func SortAscFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.OrderByAsc(value[0])

View File

@@ -36,8 +36,8 @@ var migrations = map[string]string{
id SERIAL PRIMARY KEY,
node_uuid VARCHAR(36),
username TEXT NOT NULL,
type TEXT NOT NULL,
inbound TEXT NOT NULL,
type TEXT NOT NULL,
uuid TEXT NOT NULL,
password TEXT NOT NULL,
secret TEXT NOT NULL,
@@ -100,11 +100,236 @@ var migrations = map[string]string{
);
`,
"1_initialize_schema.down.sql": `
DROP TABLE IF EXISTS squas;
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS bandwidth_limiter_to_squad;
DROP TABLE IF EXISTS bandwidth_limiters;
DROP TABLE IF EXISTS connection_limiter_to_squad;
DROP TABLE IF EXISTS connection_limiters;
DROP TABLE IF EXISTS user_to_squad;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS node_to_squad;
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS squads;
`,
"2_init_limiters_and_indexes.up.sql": `
CREATE TABLE traffic_limiters (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
mode TEXT NOT NULL,
raw_used BIGINT NOT NULL DEFAULT 0,
quota TEXT NOT NULL,
raw_quota BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE traffic_limiter_to_squad (
traffic_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (traffic_limiter_id, squad_id),
FOREIGN KEY (traffic_limiter_id) REFERENCES traffic_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE rate_limiters (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
connection_type TEXT NOT NULL,
count INTEGER NOT NULL,
interval TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE rate_limiter_to_squad (
rate_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (rate_limiter_id, squad_id),
FOREIGN KEY (rate_limiter_id) REFERENCES rate_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
ALTER TABLE bandwidth_limiters ADD COLUMN flow_keys JSONB NOT NULL DEFAULT '[]'::jsonb;
UPDATE connection_limiters SET lock_type = 'default' WHERE lock_type = '';
ALTER TABLE node_to_squad
DROP CONSTRAINT node_to_squad_squad_id_fkey,
ADD CONSTRAINT node_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE user_to_squad
DROP CONSTRAINT user_to_squad_squad_id_fkey,
ADD CONSTRAINT user_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE connection_limiter_to_squad
DROP CONSTRAINT connection_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT connection_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
ALTER TABLE bandwidth_limiter_to_squad
DROP CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE;
CREATE INDEX idx_squads_created_at ON squads(created_at);
CREATE INDEX idx_squads_updated_at ON squads(updated_at);
CREATE INDEX idx_nodes_created_at ON nodes(created_at);
CREATE INDEX idx_nodes_updated_at ON nodes(updated_at);
CREATE INDEX idx_node_to_squad_squad_id ON node_to_squad(squad_id);
CREATE INDEX idx_users_node_uuid ON users(node_uuid);
CREATE INDEX idx_users_inbound ON users(inbound);
CREATE INDEX idx_users_type ON users(type);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_users_updated_at ON users(updated_at);
CREATE INDEX idx_user_to_squad_squad_id ON user_to_squad(squad_id);
CREATE INDEX idx_connection_limiters_outbound ON connection_limiters(outbound);
CREATE INDEX idx_connection_limiters_strategy ON connection_limiters(strategy);
CREATE INDEX idx_connection_limiters_connection_type ON connection_limiters(connection_type);
CREATE INDEX idx_connection_limiters_lock_type ON connection_limiters(lock_type);
CREATE INDEX idx_connection_limiters_created_at ON connection_limiters(created_at);
CREATE INDEX idx_connection_limiters_updated_at ON connection_limiters(updated_at);
CREATE INDEX idx_connection_limiter_to_squad_squad_id ON connection_limiter_to_squad(squad_id);
CREATE INDEX idx_bandwidth_limiters_outbound ON bandwidth_limiters(outbound);
CREATE INDEX idx_bandwidth_limiters_strategy ON bandwidth_limiters(strategy);
CREATE INDEX idx_bandwidth_limiters_mode ON bandwidth_limiters(mode);
CREATE INDEX idx_bandwidth_limiters_connection_type ON bandwidth_limiters(connection_type);
CREATE INDEX idx_bandwidth_limiters_raw_speed ON bandwidth_limiters(raw_speed);
CREATE INDEX idx_bandwidth_limiters_created_at ON bandwidth_limiters(created_at);
CREATE INDEX idx_bandwidth_limiters_updated_at ON bandwidth_limiters(updated_at);
CREATE INDEX idx_bandwidth_limiter_to_squad_squad_id ON bandwidth_limiter_to_squad(squad_id);
CREATE INDEX idx_traffic_limiters_outbound ON traffic_limiters(outbound);
CREATE INDEX idx_traffic_limiters_strategy ON traffic_limiters(strategy);
CREATE INDEX idx_traffic_limiters_mode ON traffic_limiters(mode);
CREATE INDEX idx_traffic_limiters_raw_used ON traffic_limiters(raw_used);
CREATE INDEX idx_traffic_limiters_raw_quota ON traffic_limiters(raw_quota);
CREATE INDEX idx_traffic_limiters_created_at ON traffic_limiters(created_at);
CREATE INDEX idx_traffic_limiters_updated_at ON traffic_limiters(updated_at);
CREATE INDEX idx_traffic_limiter_to_squad_squad_id ON traffic_limiter_to_squad(squad_id);
CREATE INDEX idx_rate_limiters_outbound ON rate_limiters(outbound);
CREATE INDEX idx_rate_limiters_strategy ON rate_limiters(strategy);
CREATE INDEX idx_rate_limiters_connection_type ON rate_limiters(connection_type);
CREATE INDEX idx_rate_limiters_interval ON rate_limiters("interval");
CREATE INDEX idx_rate_limiters_count ON rate_limiters(count);
CREATE INDEX idx_rate_limiters_created_at ON rate_limiters(created_at);
CREATE INDEX idx_rate_limiters_updated_at ON rate_limiters(updated_at);
CREATE INDEX idx_rate_limiter_to_squad_squad_id ON rate_limiter_to_squad(squad_id);
UPDATE connection_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE bandwidth_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE rate_limiters SET connection_type = 'source_ip' WHERE connection_type = 'ip';
UPDATE bandwidth_limiters
SET flow_keys = REPLACE(flow_keys::text, '"ip"', '"source_ip"')::jsonb
WHERE flow_keys @> '["ip"]'::jsonb;
UPDATE bandwidth_limiters SET mode = 'bidirectional' WHERE mode = 'duplex';
UPDATE traffic_limiters SET mode = 'bidirectional' WHERE mode = 'duplex';
`,
"2_init_limiters_and_indexes.down.sql": `
UPDATE traffic_limiters SET mode = 'duplex' WHERE mode = 'bidirectional';
UPDATE bandwidth_limiters SET mode = 'duplex' WHERE mode = 'bidirectional';
UPDATE bandwidth_limiters
SET flow_keys = REPLACE(flow_keys::text, '"source_ip"', '"ip"')::jsonb
WHERE flow_keys @> '["source_ip"]'::jsonb;
UPDATE rate_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
UPDATE bandwidth_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
UPDATE connection_limiters SET connection_type = 'ip' WHERE connection_type = 'source_ip';
DROP INDEX IF EXISTS idx_rate_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_rate_limiters_updated_at;
DROP INDEX IF EXISTS idx_rate_limiters_created_at;
DROP INDEX IF EXISTS idx_rate_limiters_count;
DROP INDEX IF EXISTS idx_rate_limiters_interval;
DROP INDEX IF EXISTS idx_rate_limiters_connection_type;
DROP INDEX IF EXISTS idx_rate_limiters_strategy;
DROP INDEX IF EXISTS idx_rate_limiters_outbound;
DROP INDEX IF EXISTS idx_traffic_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_traffic_limiters_updated_at;
DROP INDEX IF EXISTS idx_traffic_limiters_created_at;
DROP INDEX IF EXISTS idx_traffic_limiters_raw_quota;
DROP INDEX IF EXISTS idx_traffic_limiters_raw_used;
DROP INDEX IF EXISTS idx_traffic_limiters_mode;
DROP INDEX IF EXISTS idx_traffic_limiters_strategy;
DROP INDEX IF EXISTS idx_traffic_limiters_outbound;
DROP INDEX IF EXISTS idx_bandwidth_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_bandwidth_limiters_updated_at;
DROP INDEX IF EXISTS idx_bandwidth_limiters_created_at;
DROP INDEX IF EXISTS idx_bandwidth_limiters_raw_speed;
DROP INDEX IF EXISTS idx_bandwidth_limiters_connection_type;
DROP INDEX IF EXISTS idx_bandwidth_limiters_mode;
DROP INDEX IF EXISTS idx_bandwidth_limiters_strategy;
DROP INDEX IF EXISTS idx_bandwidth_limiters_outbound;
DROP INDEX IF EXISTS idx_connection_limiter_to_squad_squad_id;
DROP INDEX IF EXISTS idx_connection_limiters_updated_at;
DROP INDEX IF EXISTS idx_connection_limiters_created_at;
DROP INDEX IF EXISTS idx_connection_limiters_lock_type;
DROP INDEX IF EXISTS idx_connection_limiters_connection_type;
DROP INDEX IF EXISTS idx_connection_limiters_strategy;
DROP INDEX IF EXISTS idx_connection_limiters_outbound;
DROP INDEX IF EXISTS idx_user_to_squad_squad_id;
DROP INDEX IF EXISTS idx_users_updated_at;
DROP INDEX IF EXISTS idx_users_created_at;
DROP INDEX IF EXISTS idx_users_type;
DROP INDEX IF EXISTS idx_users_inbound;
DROP INDEX IF EXISTS idx_users_node_uuid;
DROP INDEX IF EXISTS idx_node_to_squad_squad_id;
DROP INDEX IF EXISTS idx_nodes_updated_at;
DROP INDEX IF EXISTS idx_nodes_created_at;
DROP INDEX IF EXISTS idx_squads_updated_at;
DROP INDEX IF EXISTS idx_squads_created_at;
ALTER TABLE bandwidth_limiter_to_squad
DROP CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT bandwidth_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE connection_limiter_to_squad
DROP CONSTRAINT connection_limiter_to_squad_squad_id_fkey,
ADD CONSTRAINT connection_limiter_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE user_to_squad
DROP CONSTRAINT user_to_squad_squad_id_fkey,
ADD CONSTRAINT user_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
ALTER TABLE node_to_squad
DROP CONSTRAINT node_to_squad_squad_id_fkey,
ADD CONSTRAINT node_to_squad_squad_id_fkey
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT;
UPDATE connection_limiters SET lock_type = '' WHERE lock_type = 'default';
ALTER TABLE bandwidth_limiters DROP COLUMN flow_keys;
DROP TABLE IF EXISTS rate_limiter_to_squad;
DROP TABLE IF EXISTS rate_limiters;
DROP TABLE IF EXISTS traffic_limiter_to_squad;
DROP TABLE IF EXISTS traffic_limiters;
`,
}
@@ -113,12 +338,10 @@ func Migrate(db *sql.DB) error {
if err != nil {
return err
}
sourceDriver := source.NewRawDriver(migrations)
if err := sourceDriver.Init(); err != nil {
return err
}
m, err := migrate.NewWithInstance(
"raw",
sourceDriver,
@@ -128,6 +351,5 @@ func Migrate(db *sql.DB) error {
if err != nil {
return err
}
return m.Up()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
package postgresql
import (
"encoding/json"
"fmt"
)
type stringSliceJSON []string
func (s *stringSliceJSON) Scan(src interface{}) error {
if src == nil {
*s = nil
return nil
}
var data []byte
switch v := src.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("stringSliceJSON.Scan: unsupported type %T", src)
}
if len(data) == 0 {
*s = nil
return nil
}
return json.Unmarshal(data, (*[]string)(s))
}
func marshalStringSlice(values []string) ([]byte, error) {
if values == nil {
values = []string{}
}
return json.Marshal(values)
}

View File

@@ -0,0 +1,169 @@
package sqlite
import (
"encoding/json"
"strconv"
"github.com/huandu/go-sqlbuilder"
"github.com/sagernet/sing-box/common/byteformats"
)
type Filter func(sb *sqlbuilder.SelectBuilder, value []string) error
func EqualFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.Equal(field, value[0]))
return nil
}
}
func EqualOrNullFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.Or(sb.Equal(field, value[0]), sb.IsNull(field)))
return nil
}
}
func GreaterThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.GreaterThan(field, value[0]))
return nil
}
}
func LessThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.LessThan(field, value[0]))
return nil
}
}
func GreaterEqualThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.GreaterEqualThan(field, value[0]))
return nil
}
}
func LessEqualThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.Where(sb.LessEqualThan(field, value[0]))
return nil
}
}
func SpeedGreaterEqualThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
bytesSpeed, err := json.Marshal(value[0])
if err != nil {
return err
}
speed := &byteformats.NetworkBytesCompat{}
err = speed.UnmarshalJSON(bytesSpeed)
if err != nil {
return err
}
sb.Where(sb.GreaterEqualThan(field, speed.Value()))
return nil
}
}
func SpeedLessEqualThanFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
bytesSpeed, err := json.Marshal(value[0])
if err != nil {
return err
}
speed := &byteformats.NetworkBytesCompat{}
err = speed.UnmarshalJSON(bytesSpeed)
if err != nil {
return err
}
sb.Where(sb.LessEqualThan(field, speed.Value()))
return nil
}
}
func ExistsAndWhereInFilter(subquery *sqlbuilder.SelectBuilder, field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
values := make([]interface{}, len(value))
for i, v := range value {
values[i] = v
}
subquery.Where(subquery.In(field, values...))
sb.Where(sb.Exists(subquery))
return nil
}
}
func InFilter(field string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
if len(value) == 0 {
return nil
}
values := make([]interface{}, len(value))
for i, v := range value {
values[i] = v
}
sb.Where(sb.In(field, values...))
return nil
}
}
func SortAscFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.OrderByAsc(value[0])
return nil
}
}
func SortDescFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
sb.OrderByDesc(value[0])
return nil
}
}
func ReplacedSortAscFilter(replace map[string]string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
if replacedValue, ok := replace[value[0]]; ok {
sb.OrderByAsc(replacedValue)
} else {
sb.OrderByAsc(value[0])
}
return nil
}
}
func ReplacedSortDescFilter(replace map[string]string) Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
if replacedValue, ok := replace[value[0]]; ok {
sb.OrderByDesc(replacedValue)
} else {
sb.OrderByDesc(value[0])
}
return nil
}
}
func LimitFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
limit, err := strconv.Atoi(value[0])
if err != nil {
return err
}
sb.Limit(limit)
return nil
}
}
func OffsetFilter() Filter {
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
offset, err := strconv.Atoi(value[0])
if err != nil {
return err
}
sb.Offset(offset)
return nil
}
}

View File

@@ -0,0 +1,237 @@
package sqlite
import (
"database/sql"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite"
"github.com/sagernet/sing-box/common/migrate/source"
)
var migrations = map[string]string{
"1_initialize_schema.up.sql": `
CREATE TABLE squads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE nodes (
uuid VARCHAR(36) PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE node_to_squad (
node_uuid VARCHAR(36) NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (node_uuid, squad_id),
FOREIGN KEY (node_uuid) REFERENCES nodes(uuid) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_uuid VARCHAR(36),
username TEXT NOT NULL,
inbound TEXT NOT NULL,
type TEXT NOT NULL,
uuid TEXT NOT NULL,
password TEXT NOT NULL,
secret TEXT NOT NULL,
flow TEXT NOT NULL,
alter_id INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, inbound)
);
CREATE TABLE user_to_squad (
user_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (user_id, squad_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE connection_limiters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
connection_type TEXT NOT NULL,
lock_type TEXT NOT NULL,
count INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE connection_limiter_to_squad (
connection_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (connection_limiter_id, squad_id),
FOREIGN KEY (connection_limiter_id) REFERENCES connection_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE bandwidth_limiters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
mode TEXT NOT NULL,
connection_type TEXT NOT NULL,
speed TEXT NOT NULL,
raw_speed BIGINT NOT NULL DEFAULT 0,
flow_keys TEXT NOT NULL DEFAULT '[]',
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE bandwidth_limiter_to_squad (
bandwidth_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (bandwidth_limiter_id, squad_id),
FOREIGN KEY (bandwidth_limiter_id) REFERENCES bandwidth_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE traffic_limiters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
mode TEXT NOT NULL,
raw_used BIGINT NOT NULL DEFAULT 0,
quota TEXT NOT NULL,
raw_quota BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE traffic_limiter_to_squad (
traffic_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (traffic_limiter_id, squad_id),
FOREIGN KEY (traffic_limiter_id) REFERENCES traffic_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE TABLE rate_limiters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
outbound TEXT NOT NULL,
strategy TEXT NOT NULL,
connection_type TEXT NOT NULL,
count INTEGER NOT NULL,
interval TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
UNIQUE (username, outbound)
);
CREATE TABLE rate_limiter_to_squad (
rate_limiter_id INTEGER NOT NULL,
squad_id INTEGER NOT NULL,
PRIMARY KEY (rate_limiter_id, squad_id),
FOREIGN KEY (rate_limiter_id) REFERENCES rate_limiters(id) ON DELETE CASCADE,
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE CASCADE
);
CREATE INDEX idx_squads_created_at ON squads(created_at);
CREATE INDEX idx_squads_updated_at ON squads(updated_at);
CREATE INDEX idx_nodes_created_at ON nodes(created_at);
CREATE INDEX idx_nodes_updated_at ON nodes(updated_at);
CREATE INDEX idx_node_to_squad_squad_id ON node_to_squad(squad_id);
CREATE INDEX idx_users_node_uuid ON users(node_uuid);
CREATE INDEX idx_users_inbound ON users(inbound);
CREATE INDEX idx_users_type ON users(type);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_users_updated_at ON users(updated_at);
CREATE INDEX idx_user_to_squad_squad_id ON user_to_squad(squad_id);
CREATE INDEX idx_connection_limiters_outbound ON connection_limiters(outbound);
CREATE INDEX idx_connection_limiters_strategy ON connection_limiters(strategy);
CREATE INDEX idx_connection_limiters_connection_type ON connection_limiters(connection_type);
CREATE INDEX idx_connection_limiters_lock_type ON connection_limiters(lock_type);
CREATE INDEX idx_connection_limiters_created_at ON connection_limiters(created_at);
CREATE INDEX idx_connection_limiters_updated_at ON connection_limiters(updated_at);
CREATE INDEX idx_connection_limiter_to_squad_squad_id ON connection_limiter_to_squad(squad_id);
CREATE INDEX idx_bandwidth_limiters_outbound ON bandwidth_limiters(outbound);
CREATE INDEX idx_bandwidth_limiters_strategy ON bandwidth_limiters(strategy);
CREATE INDEX idx_bandwidth_limiters_mode ON bandwidth_limiters(mode);
CREATE INDEX idx_bandwidth_limiters_connection_type ON bandwidth_limiters(connection_type);
CREATE INDEX idx_bandwidth_limiters_raw_speed ON bandwidth_limiters(raw_speed);
CREATE INDEX idx_bandwidth_limiters_created_at ON bandwidth_limiters(created_at);
CREATE INDEX idx_bandwidth_limiters_updated_at ON bandwidth_limiters(updated_at);
CREATE INDEX idx_bandwidth_limiter_to_squad_squad_id ON bandwidth_limiter_to_squad(squad_id);
CREATE INDEX idx_traffic_limiters_outbound ON traffic_limiters(outbound);
CREATE INDEX idx_traffic_limiters_strategy ON traffic_limiters(strategy);
CREATE INDEX idx_traffic_limiters_mode ON traffic_limiters(mode);
CREATE INDEX idx_traffic_limiters_raw_used ON traffic_limiters(raw_used);
CREATE INDEX idx_traffic_limiters_raw_quota ON traffic_limiters(raw_quota);
CREATE INDEX idx_traffic_limiters_created_at ON traffic_limiters(created_at);
CREATE INDEX idx_traffic_limiters_updated_at ON traffic_limiters(updated_at);
CREATE INDEX idx_traffic_limiter_to_squad_squad_id ON traffic_limiter_to_squad(squad_id);
CREATE INDEX idx_rate_limiters_outbound ON rate_limiters(outbound);
CREATE INDEX idx_rate_limiters_strategy ON rate_limiters(strategy);
CREATE INDEX idx_rate_limiters_connection_type ON rate_limiters(connection_type);
CREATE INDEX idx_rate_limiters_interval ON rate_limiters("interval");
CREATE INDEX idx_rate_limiters_count ON rate_limiters(count);
CREATE INDEX idx_rate_limiters_created_at ON rate_limiters(created_at);
CREATE INDEX idx_rate_limiters_updated_at ON rate_limiters(updated_at);
CREATE INDEX idx_rate_limiter_to_squad_squad_id ON rate_limiter_to_squad(squad_id);
`,
"1_initialize_schema.down.sql": `
DROP TABLE IF EXISTS rate_limiter_to_squad;
DROP TABLE IF EXISTS rate_limiters;
DROP TABLE IF EXISTS traffic_limiter_to_squad;
DROP TABLE IF EXISTS traffic_limiters;
DROP TABLE IF EXISTS bandwidth_limiter_to_squad;
DROP TABLE IF EXISTS bandwidth_limiters;
DROP TABLE IF EXISTS connection_limiter_to_squad;
DROP TABLE IF EXISTS connection_limiters;
DROP TABLE IF EXISTS user_to_squad;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS node_to_squad;
DROP TABLE IF EXISTS nodes;
DROP TABLE IF EXISTS squads;
`,
}
func Migrate(db *sql.DB) error {
driver, err := sqlite.WithInstance(db, &sqlite.Config{})
if err != nil {
return err
}
sourceDriver := source.NewRawDriver(migrations)
if err := sourceDriver.Init(); err != nil {
return err
}
m, err := migrate.NewWithInstance(
"raw",
sourceDriver,
"sqlite",
driver,
)
if err != nil {
return err
}
return m.Up()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
package sqlite
import (
"encoding/json"
"fmt"
)
type intSliceJSON []int
func (s *intSliceJSON) Scan(src interface{}) error {
if src == nil {
*s = nil
return nil
}
var data []byte
switch v := src.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("intSliceJSON.Scan: unsupported type %T", src)
}
if len(data) == 0 {
*s = nil
return nil
}
return json.Unmarshal(data, (*[]int)(s))
}
type stringSliceJSON []string
func (s *stringSliceJSON) Scan(src interface{}) error {
if src == nil {
*s = nil
return nil
}
var data []byte
switch v := src.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("stringSliceJSON.Scan: unsupported type %T", src)
}
if len(data) == 0 {
*s = nil
return nil
}
return json.Unmarshal(data, (*[]string)(s))
}
func marshalStringSlice(values []string) (string, error) {
if values == nil {
values = []string{}
}
data, err := json.Marshal(values)
if err != nil {
return "", err
}
return string(data), nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/go-playground/validator/v10"
"github.com/gofrs/uuid/v5"
"github.com/patrickmn/go-cache/v2"
"github.com/sagernet/sing-box/adapter"
boxService "github.com/sagernet/sing-box/adapter/service"
C "github.com/sagernet/sing-box/constant"
@@ -18,7 +17,9 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/service/manager/constant"
"github.com/sagernet/sing-box/service/manager/repository/postgresql"
"github.com/sagernet/sing-box/service/manager/repository/sqlite"
E "github.com/sagernet/sing/common/exceptions"
"github.com/shtorm-7/go-cache/v2"
)
func RegisterService(registry *boxService.Registry) {
@@ -33,12 +34,13 @@ type Service struct {
nodes map[string]constant.ConnectedNode
limiterLocks map[int]map[string]*cache.Cache[string, struct{}]
trafficUsage map[int]*TrafficUsage
userValidator *validator.Validate
defaultValidator *validator.Validate
mtx sync.RWMutex
connLockMtx sync.Mutex
trafficMtx sync.Mutex
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.ManagerServiceOptions) (adapter.Service, error) {
@@ -50,11 +52,16 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
if err != nil {
return nil, err
}
case "sqlite":
repository, err = sqlite.NewSQLiteRepository(ctx, options.Database.DSN)
if err != nil {
return nil, err
}
default:
return nil, E.New("unknown driver \"", options.Database.Driver, "\"")
}
userValidator := validator.New()
userValidator.RegisterStructValidation(func(sl validator.StructLevel) {
defaultValidator := validator.New()
defaultValidator.RegisterStructValidation(func(sl validator.StructLevel) {
user := sl.Current().Interface().(constant.UserCreate)
switch user.Type {
case "vless":
@@ -85,16 +92,41 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
}
}
}, constant.UserCreate{})
return &Service{
validateRateLimiterInterval := func(sl validator.StructLevel, interval string) {
if interval == "" {
return
}
if _, err := time.ParseDuration(interval); err != nil {
sl.ReportError(interval, "interval", "Interval", "duration", "")
}
}
defaultValidator.RegisterStructValidation(func(sl validator.StructLevel) {
validateRateLimiterInterval(sl, sl.Current().Interface().(constant.RateLimiterCreate).Interval)
}, constant.RateLimiterCreate{})
defaultValidator.RegisterStructValidation(func(sl validator.StructLevel) {
validateRateLimiterInterval(sl, sl.Current().Interface().(constant.RateLimiterUpdate).Interval)
}, constant.RateLimiterUpdate{})
service := &Service{
Adapter: boxService.NewAdapter(C.TypeManager, tag),
ctx: ctx,
logger: logger,
repository: repository,
nodes: make(map[string]constant.ConnectedNode, 0),
limiterLocks: make(map[int]map[string]*cache.Cache[string, struct{}]),
userValidator: userValidator,
defaultValidator: validator.New(),
}, nil
trafficUsage: make(map[int]*TrafficUsage),
defaultValidator: defaultValidator,
}
limiters, err := service.repository.GetTrafficLimiters(map[string][]string{})
if err != nil {
return nil, err
}
for _, limiter := range limiters {
service.trafficUsage[limiter.ID] = &TrafficUsage{
used: limiter.RawUsed,
quota: limiter.RawQuota,
}
}
return service, nil
}
func (s *Service) CreateSquad(node constant.SquadCreate) (constant.Squad, error) {
@@ -139,12 +171,70 @@ func (s *Service) UpdateSquad(id int, squad constant.SquadUpdate) (constant.Squa
func (s *Service) DeleteSquad(id int) (constant.Squad, error) {
s.mtx.Lock()
s.trafficMtx.Lock()
s.connLockMtx.Lock()
defer s.mtx.Unlock()
deletedSquad, err := s.repository.DeleteSquad(id)
defer s.trafficMtx.Unlock()
defer s.connLockMtx.Unlock()
deleted, err := s.repository.DeleteSquad(id)
if err != nil {
return deletedSquad, err
return deleted.Squad, err
}
return deletedSquad, nil
for _, uuid := range deleted.OrphanedNodeUUIDs {
if connectedNode, ok := s.nodes[uuid]; ok {
connectedNode.Close()
delete(s.nodes, uuid)
}
}
for _, lid := range deleted.OrphanedTrafficLimiterIDs {
delete(s.trafficUsage, lid)
}
for _, lid := range deleted.OrphanedConnectionLimiterIDs {
delete(s.limiterLocks, lid)
}
for _, uuid := range deleted.SurvivingNodeUUIDs {
connectedNode, ok := s.nodes[uuid]
if !ok {
continue
}
node, err := s.repository.GetNode(uuid)
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
squadIDs := convertIntSliceToStringSlice(node.SquadIDs)
users, err := s.repository.GetUsers(map[string][]string{"squad_id_in": squadIDs})
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
connectedNode.UpdateUsers(users)
bandwidthLimiters, err := s.repository.GetBandwidthLimiters(map[string][]string{"squad_id_in": squadIDs})
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
connectedNode.UpdateBandwidthLimiters(bandwidthLimiters)
trafficLimiters, err := s.repository.GetTrafficLimiters(map[string][]string{"squad_id_in": squadIDs})
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
connectedNode.UpdateTrafficLimiters(trafficLimiters)
connectionLimiters, err := s.repository.GetConnectionLimiters(map[string][]string{"squad_id_in": squadIDs})
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
connectedNode.UpdateConnectionLimiters(connectionLimiters)
rateLimiters, err := s.repository.GetRateLimiters(map[string][]string{"squad_id_in": squadIDs})
if err != nil {
s.closeAllNodes()
return deleted.Squad, err
}
connectedNode.UpdateRateLimiters(rateLimiters)
}
return deleted.Squad, nil
}
func (s *Service) CreateNode(node constant.NodeCreate) (constant.Node, error) {
@@ -173,14 +263,14 @@ func (s *Service) GetNode(uuid string) (constant.Node, error) {
return s.repository.GetNode(uuid)
}
func (s *Service) GetNodeStatus(uuid string) string {
func (s *Service) GetNodeStatus(uuid string) (string, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
node, ok := s.nodes[uuid]
if !ok || !node.IsOnline() {
return "offline"
return "offline", nil
}
return "online"
return "online", nil
}
func (s *Service) UpdateNode(uuid string, node constant.NodeUpdate) (constant.Node, error) {
@@ -215,7 +305,7 @@ func (s *Service) DeleteNode(uuid string) (constant.Node, error) {
func (s *Service) CreateUser(user constant.UserCreate) (constant.User, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.userValidator.Struct(user)
err := s.defaultValidator.Struct(user)
if err != nil {
return constant.User{}, err
}
@@ -294,6 +384,221 @@ func (s *Service) DeleteUser(id int) (constant.User, error) {
return deletedUser, nil
}
func (s *Service) CreateBandwidthLimiter(limiter constant.BandwidthLimiterCreate) (constant.BandwidthLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.BandwidthLimiter{}, err
}
createdLimiter, err := s.repository.CreateBandwidthLimiter(limiter)
if err != nil {
return createdLimiter, err
}
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(createdLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return createdLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateBandwidthLimiter(createdLimiter)
}
}
return createdLimiter, nil
}
func (s *Service) GetBandwidthLimiters(filters map[string][]string) ([]constant.BandwidthLimiter, error) {
return s.repository.GetBandwidthLimiters(filters)
}
func (s *Service) GetBandwidthLimitersCount(filters map[string][]string) (int, error) {
return s.repository.GetBandwidthLimitersCount(filters)
}
func (s *Service) GetBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
return s.repository.GetBandwidthLimiter(id)
}
func (s *Service) UpdateBandwidthLimiter(id int, limiter constant.BandwidthLimiterUpdate) (constant.BandwidthLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.BandwidthLimiter{}, err
}
updatedLimiter, err := s.repository.UpdateBandwidthLimiter(id, limiter)
if err != nil {
return updatedLimiter, err
}
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(updatedLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return updatedLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateBandwidthLimiter(updatedLimiter)
}
}
return updatedLimiter, nil
}
func (s *Service) DeleteBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
deletedLimiter, err := s.repository.DeleteBandwidthLimiter(id)
if err != nil {
return deletedLimiter, err
}
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(deletedLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return deletedLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.DeleteBandwidthLimiter(deletedLimiter)
}
}
return deletedLimiter, nil
}
func (s *Service) CreateTrafficLimiter(limiter constant.TrafficLimiterCreate) (constant.TrafficLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.TrafficLimiter{}, err
}
createdLimiter, err := s.repository.CreateTrafficLimiter(limiter)
if err != nil {
return createdLimiter, err
}
s.trafficMtx.Lock()
s.trafficUsage[createdLimiter.ID] = &TrafficUsage{
used: createdLimiter.RawUsed,
quota: createdLimiter.RawQuota,
}
s.trafficMtx.Unlock()
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(createdLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return createdLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateTrafficLimiter(createdLimiter)
}
}
return createdLimiter, nil
}
func (s *Service) GetTrafficLimiters(filters map[string][]string) ([]constant.TrafficLimiter, error) {
return s.repository.GetTrafficLimiters(filters)
}
func (s *Service) GetTrafficLimitersCount(filters map[string][]string) (int, error) {
return s.repository.GetTrafficLimitersCount(filters)
}
func (s *Service) GetTrafficLimiter(id int) (constant.TrafficLimiter, error) {
return s.repository.GetTrafficLimiter(id)
}
func (s *Service) UpdateTrafficLimiter(id int, limiter constant.TrafficLimiterUpdate) (constant.TrafficLimiter, error) {
s.mtx.Lock()
s.trafficMtx.Lock()
defer s.mtx.Unlock()
defer s.trafficMtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.TrafficLimiter{}, err
}
updatedLimiter, err := s.repository.UpdateTrafficLimiter(id, limiter)
if err != nil {
return updatedLimiter, err
}
s.trafficUsage[updatedLimiter.ID] = &TrafficUsage{
used: updatedLimiter.RawUsed,
quota: updatedLimiter.RawQuota,
}
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(updatedLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return updatedLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateTrafficLimiter(updatedLimiter)
}
}
return updatedLimiter, nil
}
func (s *Service) UpdateTrafficLimiterUsed(id int, used uint64) (constant.TrafficLimiter, error) {
s.mtx.Lock()
s.trafficMtx.Lock()
defer s.mtx.Unlock()
defer s.trafficMtx.Unlock()
updatedLimiter, err := s.repository.UpdateTrafficLimiterUsed(id, used)
if err != nil {
return updatedLimiter, err
}
s.trafficUsage[updatedLimiter.ID] = &TrafficUsage{
used: updatedLimiter.RawUsed,
quota: updatedLimiter.RawQuota,
}
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(updatedLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return updatedLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateTrafficLimiter(updatedLimiter)
}
}
return updatedLimiter, nil
}
func (s *Service) DeleteTrafficLimiter(id int) (constant.TrafficLimiter, error) {
s.mtx.Lock()
s.trafficMtx.Lock()
defer s.mtx.Unlock()
defer s.trafficMtx.Unlock()
deletedLimiter, err := s.repository.DeleteTrafficLimiter(id)
if err != nil {
return deletedLimiter, err
}
delete(s.trafficUsage, deletedLimiter.ID)
nodes, err := s.repository.GetNodes(map[string][]string{
"squad_id_in": convertIntSliceToStringSlice(deletedLimiter.SquadIDs),
})
if err != nil {
s.closeAllNodes()
return deletedLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.DeleteTrafficLimiter(deletedLimiter)
}
}
return deletedLimiter, nil
}
func (s *Service) CreateConnectionLimiter(limiter constant.ConnectionLimiterCreate) (constant.ConnectionLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
@@ -390,14 +695,14 @@ func (s *Service) DeleteConnectionLimiter(id int) (constant.ConnectionLimiter, e
return deletedLimiter, nil
}
func (s *Service) CreateBandwidthLimiter(limiter constant.BandwidthLimiterCreate) (constant.BandwidthLimiter, error) {
func (s *Service) CreateRateLimiter(limiter constant.RateLimiterCreate) (constant.RateLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.BandwidthLimiter{}, err
return constant.RateLimiter{}, err
}
createdLimiter, err := s.repository.CreateBandwidthLimiter(limiter)
createdLimiter, err := s.repository.CreateRateLimiter(limiter)
if err != nil {
return createdLimiter, err
}
@@ -410,32 +715,32 @@ func (s *Service) CreateBandwidthLimiter(limiter constant.BandwidthLimiterCreate
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateBandwidthLimiter(createdLimiter)
node.UpdateRateLimiter(createdLimiter)
}
}
return createdLimiter, nil
}
func (s *Service) GetBandwidthLimiters(filters map[string][]string) ([]constant.BandwidthLimiter, error) {
return s.repository.GetBandwidthLimiters(filters)
func (s *Service) GetRateLimiters(filters map[string][]string) ([]constant.RateLimiter, error) {
return s.repository.GetRateLimiters(filters)
}
func (s *Service) GetBandwidthLimitersCount(filters map[string][]string) (int, error) {
return s.repository.GetBandwidthLimitersCount(filters)
func (s *Service) GetRateLimitersCount(filters map[string][]string) (int, error) {
return s.repository.GetRateLimitersCount(filters)
}
func (s *Service) GetBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
return s.repository.GetBandwidthLimiter(id)
func (s *Service) GetRateLimiter(id int) (constant.RateLimiter, error) {
return s.repository.GetRateLimiter(id)
}
func (s *Service) UpdateBandwidthLimiter(id int, limiter constant.BandwidthLimiterUpdate) (constant.BandwidthLimiter, error) {
func (s *Service) UpdateRateLimiter(id int, limiter constant.RateLimiterUpdate) (constant.RateLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
err := s.defaultValidator.Struct(limiter)
if err != nil {
return constant.BandwidthLimiter{}, err
return constant.RateLimiter{}, err
}
updatedLimiter, err := s.repository.UpdateBandwidthLimiter(id, limiter)
updatedLimiter, err := s.repository.UpdateRateLimiter(id, limiter)
if err != nil {
return updatedLimiter, err
}
@@ -448,16 +753,16 @@ func (s *Service) UpdateBandwidthLimiter(id int, limiter constant.BandwidthLimit
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.UpdateBandwidthLimiter(updatedLimiter)
node.UpdateRateLimiter(updatedLimiter)
}
}
return updatedLimiter, nil
}
func (s *Service) DeleteBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
func (s *Service) DeleteRateLimiter(id int) (constant.RateLimiter, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
deletedLimiter, err := s.repository.DeleteBandwidthLimiter(id)
deletedLimiter, err := s.repository.DeleteRateLimiter(id)
if err != nil {
return deletedLimiter, err
}
@@ -470,7 +775,7 @@ func (s *Service) DeleteBandwidthLimiter(id int) (constant.BandwidthLimiter, err
}
for _, node := range nodes {
if node, ok := s.nodes[node.UUID]; ok {
node.DeleteBandwidthLimiter(deletedLimiter)
node.DeleteRateLimiter(deletedLimiter)
}
}
return deletedLimiter, nil
@@ -500,6 +805,13 @@ func (s *Service) AddNode(uuid string, node constant.ConnectedNode) error {
return err
}
node.UpdateBandwidthLimiters(bandwidthLimiters)
trafficLimiters, err := s.repository.GetTrafficLimiters(map[string][]string{
"squad_id_in": squadIDs,
})
if err != nil {
return err
}
node.UpdateTrafficLimiters(trafficLimiters)
connectionLimiters, err := s.repository.GetConnectionLimiters(map[string][]string{
"squad_id_in": squadIDs,
})
@@ -507,6 +819,13 @@ func (s *Service) AddNode(uuid string, node constant.ConnectedNode) error {
return err
}
node.UpdateConnectionLimiters(connectionLimiters)
rateLimiters, err := s.repository.GetRateLimiters(map[string][]string{
"squad_id_in": squadIDs,
})
if err != nil {
return err
}
node.UpdateRateLimiters(rateLimiters)
s.nodes[uuid] = node
return nil
}
@@ -579,6 +898,25 @@ func (s *Service) ReleaseLock(limiterId int, id string, handleId string) error {
return nil
}
func (s *Service) AddTrafficUsage(limiterId int, n uint64) (uint64, error) {
s.trafficMtx.Lock()
defer s.trafficMtx.Unlock()
trafficStat, ok := s.trafficUsage[limiterId]
if !ok {
return 0, E.New("limiter not found")
}
trafficStat.used = trafficStat.used + n
if trafficStat.used > trafficStat.quota {
trafficStat.used = trafficStat.quota
}
updatedLimiter, err := s.repository.UpdateTrafficLimiterUsed(limiterId, trafficStat.used)
if err != nil {
return 0, err
}
trafficStat.used = updatedLimiter.RawUsed
return trafficStat.used, nil
}
func (s *Service) Start(stage adapter.StartStage) error {
return nil
}
@@ -587,6 +925,11 @@ func (s *Service) Close() error {
return nil
}
type TrafficUsage struct {
used uint64
quota uint64
}
func (s *Service) closeAllNodes() {
for _, node := range s.nodes {
node.Close()