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

@@ -0,0 +1,74 @@
package byteformats
import (
"fmt"
"math"
)
var (
unitNames = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iUnitNames = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
kUnitNames = []string{"kB", "MB", "GB", "TB", "PB", "EB"}
kiUnitNames = []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func formatBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func formatKBytes(s uint64, base float64, sizes []string) string {
if s == 0 {
return fmt.Sprintf("0 %s", sizes[0])
}
e := math.Floor(logn(float64(s), base))
if e < 1 {
e = 1
}
suffix := sizes[int(e)-1]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func FormatBytes(s uint64) string {
return formatBytes(s, 1000, unitNames)
}
func FormatMemoryBytes(s uint64) string {
return formatBytes(s, 1024, unitNames)
}
func FormatIBytes(s uint64) string {
return formatBytes(s, 1024, iUnitNames)
}
func FormatKBytes(s uint64) string {
return formatKBytes(s, 1000, kUnitNames)
}
func FormatMemoryKBytes(s uint64) string {
return formatKBytes(s, 1024, kUnitNames)
}
func FormatKIBytes(s uint64) string {
return formatKBytes(s, 1024, kiUnitNames)
}

218
common/byteformats/json.go Normal file
View File

@@ -0,0 +1,218 @@
package byteformats
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
const (
KByte = Byte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var unitValueTable = map[string]uint64{
"b": Byte,
"k": KByte,
"kb": KByte,
"ki": KiByte,
"kib": KiByte,
"m": MByte,
"mb": MByte,
"mi": MiByte,
"mib": MiByte,
"g": GByte,
"gb": GByte,
"gi": GiByte,
"gib": GiByte,
"t": TByte,
"tb": TByte,
"ti": TiByte,
"tib": TiByte,
"p": PByte,
"pb": PByte,
"pi": PiByte,
"pib": PiByte,
"e": EByte,
"eb": EByte,
"ei": EiByte,
"eib": EiByte,
}
var memoryUnitValueTable = map[string]uint64{
"b": Byte,
"k": KiByte,
"kb": KiByte,
"m": MiByte,
"mb": MiByte,
"g": GiByte,
"gb": GiByte,
"t": TiByte,
"tb": TiByte,
"p": PiByte,
"pb": PiByte,
"e": EiByte,
"eb": EiByte,
}
var networkUnitValueTable = map[string]uint64{
"Bps": Byte,
"Kbps": KByte / 8,
"KBps": KByte,
"Mbps": MByte / 8,
"MBps": MByte,
"Gbps": GByte / 8,
"GBps": GByte,
"Tbps": TByte / 8,
"TBps": TByte,
"Pbps": PByte / 8,
"PBps": PByte,
"Ebps": EByte / 8,
"EBps": EByte,
}
type rawBytes struct {
value uint64
unit string
unitValue uint64
}
func (b rawBytes) MarshalJSON() ([]byte, error) {
if b.unit == "" {
return json.Marshal(b.value)
}
return json.Marshal(strconv.FormatUint(b.value/b.unitValue, 10) + b.unit)
}
func parseUnit(b *rawBytes, unitTable map[string]uint64, caseSensitive bool, bytes []byte) error {
var intValue int64
err := json.Unmarshal(bytes, &intValue)
if err == nil {
b.value = uint64(intValue)
b.unit = ""
b.unitValue = 1
return nil
}
var stringValue string
err = json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
if strings.TrimSpace(stringValue) == "" {
b.value = 0
b.unit = ""
b.unitValue = 1
return nil
}
unitIndex := 0
for i, c := range stringValue {
if c < '0' || c > '9' {
unitIndex = i
break
}
}
if unitIndex == 0 {
return fmt.Errorf("invalid format: %s", stringValue)
}
value, err := strconv.ParseUint(stringValue[:unitIndex], 10, 64)
if err != nil {
return fmt.Errorf("parse %s: %w", stringValue[:unitIndex], err)
}
rawUnit := stringValue[unitIndex:]
var unit string
if caseSensitive {
unit = strings.TrimSpace(rawUnit)
} else {
unit = strings.TrimSpace(strings.ToLower(rawUnit))
}
unitValue, loaded := unitTable[unit]
if !loaded {
return fmt.Errorf("unsupported unit: %s", rawUnit)
}
b.value = value * unitValue
b.unit = rawUnit
b.unitValue = unitValue
return nil
}
type Bytes struct {
rawBytes
}
func (b *Bytes) Value() uint64 {
if b == nil {
return 0
}
return b.value
}
func (b *Bytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&b.rawBytes, unitValueTable, false, bytes)
}
type MemoryBytes struct {
rawBytes
}
func (m *MemoryBytes) Value() uint64 {
if m == nil {
return 0
}
return m.value
}
func (m *MemoryBytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&m.rawBytes, memoryUnitValueTable, false, bytes)
}
type NetworkBytes struct {
rawBytes
}
func (n *NetworkBytes) Value() uint64 {
if n == nil {
return 0
}
return n.value
}
func (n *NetworkBytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
}
type NetworkBytesCompat struct {
rawBytes
}
func (n *NetworkBytesCompat) Value() uint64 {
if n == nil {
return 0
}
return n.value
}
func (n *NetworkBytesCompat) UnmarshalJSON(bytes []byte) error {
err := parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
if err != nil {
newErr := parseUnit(&n.rawBytes, unitValueTable, false, bytes)
if newErr == nil {
return nil
}
}
return err
}

View File

@@ -0,0 +1,114 @@
package byteformats_test
import (
"encoding/json"
"testing"
"github.com/sagernet/sing-box/common/byteformats"
"github.com/stretchr/testify/require"
)
func TestNetworkBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 Bps": byteformats.Byte,
"1 Kbps": byteformats.KByte / 8,
"1 KBps": byteformats.KByte,
"1 Mbps": byteformats.MByte / 8,
"1 MBps": byteformats.MByte,
"1 Gbps": byteformats.GByte / 8,
"1 GBps": byteformats.GByte,
"1 Tbps": byteformats.TByte / 8,
"1 TBps": byteformats.TByte,
"1 Pbps": byteformats.PByte / 8,
"1 PBps": byteformats.PByte,
"1k": byteformats.KByte,
"1m": byteformats.MByte,
}
for k, v := range testMap {
var nb byteformats.NetworkBytesCompat
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &nb))
require.Equal(t, v, nb.Value())
b, err := json.Marshal(nb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}
func TestMemoryBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 B": byteformats.Byte,
"1 KB": byteformats.KiByte,
"1 MB": byteformats.MiByte,
"1 GB": byteformats.GiByte,
"1 TB": byteformats.TiByte,
"1 PB": byteformats.PiByte,
}
for k, v := range testMap {
var mb byteformats.MemoryBytes
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
require.Equal(t, v, mb.Value())
b, err := json.Marshal(mb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}
func TestDefaultBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 B": byteformats.Byte,
"1 KB": byteformats.KByte,
"1 KiB": byteformats.KiByte,
"1 MB": byteformats.MByte,
"1 MiB": byteformats.MiByte,
"1 GB": byteformats.GByte,
"1 GiB": byteformats.GiByte,
"1 TB": byteformats.TByte,
"1 TiB": byteformats.TiByte,
"1 PB": byteformats.PByte,
"1 PiB": byteformats.PiByte,
"1 EB": byteformats.EByte,
"1 EiB": byteformats.EiByte,
"1k": byteformats.KByte,
"1m": byteformats.MByte,
"1g": byteformats.GByte,
"1t": byteformats.TByte,
"1p": byteformats.PByte,
"1e": byteformats.EByte,
"1K": byteformats.KByte,
"1M": byteformats.MByte,
"1G": byteformats.GByte,
"1T": byteformats.TByte,
"1P": byteformats.PByte,
"1E": byteformats.EByte,
"1Ki": byteformats.KiByte,
"1Mi": byteformats.MiByte,
"1Gi": byteformats.GiByte,
"1Ti": byteformats.TiByte,
"1Pi": byteformats.PiByte,
"1Ei": byteformats.EiByte,
"1KiB": byteformats.KiByte,
"1MiB": byteformats.MiByte,
"1GiB": byteformats.GiByte,
"1TiB": byteformats.TiByte,
"1PiB": byteformats.PiByte,
"1EiB": byteformats.EiByte,
"1kB": byteformats.KByte,
"1mB": byteformats.MByte,
"1gB": byteformats.GByte,
"1tB": byteformats.TByte,
"1pB": byteformats.PByte,
"1eB": byteformats.EByte,
}
for k, v := range testMap {
var mb byteformats.Bytes
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
require.Equal(t, v, mb.Value())
b, err := json.Marshal(mb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}