mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-09 21:28:56 +00:00
Proxy: Add Hysteria outbound & transport (version 2, udphop) and Salamander udpmask (#5508)
https://github.com/XTLS/Xray-core/issues/3547#issuecomment-3549896520 https://github.com/XTLS/Xray-core/issues/2635#issuecomment-3570871754
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PortUnion is a collection of multiple port ranges.
|
||||
type PortUnion []PortRange
|
||||
|
||||
// PortRange represents a range of ports.
|
||||
// Start and End are inclusive. [Start, End]
|
||||
type PortRange struct {
|
||||
Start, End uint16
|
||||
}
|
||||
|
||||
// ParsePortUnion parses a string of comma-separated port ranges (or single ports) into a PortUnion.
|
||||
// Returns nil if the input is invalid.
|
||||
// The returned PortUnion is guaranteed to be normalized.
|
||||
func ParsePortUnion(s string) PortUnion {
|
||||
if s == "all" || s == "*" {
|
||||
// Wildcard special case
|
||||
return PortUnion{PortRange{0, 65535}}
|
||||
}
|
||||
var result PortUnion
|
||||
portStrs := strings.Split(s, ",")
|
||||
for _, portStr := range portStrs {
|
||||
if strings.Contains(portStr, "-") {
|
||||
// Port range
|
||||
portRange := strings.Split(portStr, "-")
|
||||
if len(portRange) != 2 {
|
||||
return nil
|
||||
}
|
||||
start, err := strconv.ParseUint(portRange[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
end, err := strconv.ParseUint(portRange[1], 10, 16)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if start > end {
|
||||
start, end = end, start
|
||||
}
|
||||
result = append(result, PortRange{uint16(start), uint16(end)})
|
||||
} else {
|
||||
// Single port
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result = append(result, PortRange{uint16(port), uint16(port)})
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
return result.Normalize()
|
||||
}
|
||||
|
||||
// Normalize normalizes a PortUnion.
|
||||
// No overlapping ranges, ranges are sorted from low to high.
|
||||
func (u PortUnion) Normalize() PortUnion {
|
||||
if len(u) == 0 {
|
||||
return u
|
||||
}
|
||||
sort.Slice(u, func(i, j int) bool {
|
||||
if u[i].Start == u[j].Start {
|
||||
return u[i].End < u[j].End
|
||||
}
|
||||
return u[i].Start < u[j].Start
|
||||
})
|
||||
normalized := PortUnion{u[0]}
|
||||
for _, current := range u[1:] {
|
||||
last := &normalized[len(normalized)-1]
|
||||
if uint32(current.Start) <= uint32(last.End)+1 {
|
||||
if current.End > last.End {
|
||||
last.End = current.End
|
||||
}
|
||||
} else {
|
||||
normalized = append(normalized, current)
|
||||
}
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// Ports returns all ports in the PortUnion as a slice.
|
||||
func (u PortUnion) Ports() []uint16 {
|
||||
var ports []uint16
|
||||
for _, r := range u {
|
||||
for i := uint32(r.Start); i <= uint32(r.End); i++ {
|
||||
ports = append(ports, uint16(i))
|
||||
}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
// Contains returns true if the PortUnion contains the given port.
|
||||
func (u PortUnion) Contains(port uint16) bool {
|
||||
for _, r := range u {
|
||||
if port >= r.Start && port <= r.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsePortUnion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
want PortUnion
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
s: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "all 1",
|
||||
s: "all",
|
||||
want: PortUnion{{0, 65535}},
|
||||
},
|
||||
{
|
||||
name: "all 2",
|
||||
s: "*",
|
||||
want: PortUnion{{0, 65535}},
|
||||
},
|
||||
{
|
||||
name: "single port",
|
||||
s: "1234",
|
||||
want: PortUnion{{1234, 1234}},
|
||||
},
|
||||
{
|
||||
name: "multiple ports (unsorted)",
|
||||
s: "5678,1234,9012",
|
||||
want: PortUnion{{1234, 1234}, {5678, 5678}, {9012, 9012}},
|
||||
},
|
||||
{
|
||||
name: "one range",
|
||||
s: "1234-1240",
|
||||
want: PortUnion{{1234, 1240}},
|
||||
},
|
||||
{
|
||||
name: "one range (reversed)",
|
||||
s: "1240-1234",
|
||||
want: PortUnion{{1234, 1240}},
|
||||
},
|
||||
{
|
||||
name: "multiple ports and ranges (reversed, unsorted, overlapping)",
|
||||
s: "5678,1200-1236,9100-9012,1234-1240",
|
||||
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}},
|
||||
},
|
||||
{
|
||||
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping)",
|
||||
s: "5678,1200-1236,65531-65535,65532-65534,9100-9012,1234-1240",
|
||||
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
|
||||
},
|
||||
{
|
||||
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping) 2",
|
||||
s: "5678,1200-1236,65532-65535,65531-65534,9100-9012,1234-1240",
|
||||
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
|
||||
},
|
||||
{
|
||||
name: "invalid 1",
|
||||
s: "1234-",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid 2",
|
||||
s: "1234-ggez",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid 3",
|
||||
s: "233,",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid 4",
|
||||
s: "1234-1240-1250",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid 5",
|
||||
s: "-,,",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid 6",
|
||||
s: "http",
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ParsePortUnion(tt.s); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParsePortUnion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortUnion_Ports(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pu PortUnion
|
||||
want []uint16
|
||||
}{
|
||||
{
|
||||
name: "single port",
|
||||
pu: PortUnion{{1234, 1234}},
|
||||
want: []uint16{1234},
|
||||
},
|
||||
{
|
||||
name: "multiple ports",
|
||||
pu: PortUnion{{1234, 1236}},
|
||||
want: []uint16{1234, 1235, 1236},
|
||||
},
|
||||
{
|
||||
name: "multiple ports and ranges",
|
||||
pu: PortUnion{{1234, 1236}, {5678, 5680}, {9000, 9002}},
|
||||
want: []uint16{1234, 1235, 1236, 5678, 5679, 5680, 9000, 9001, 9002},
|
||||
},
|
||||
{
|
||||
name: "single port 65535",
|
||||
pu: PortUnion{{65535, 65535}},
|
||||
want: []uint16{65535},
|
||||
},
|
||||
{
|
||||
name: "port range with 65535",
|
||||
pu: PortUnion{{65530, 65535}},
|
||||
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535},
|
||||
},
|
||||
{
|
||||
name: "multiple ports and ranges with 65535",
|
||||
pu: PortUnion{{65530, 65535}, {1234, 1236}},
|
||||
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535, 1234, 1235, 1236},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.pu.Ports(); !slices.Equal(got, tt.want) {
|
||||
t.Errorf("PortUnion.Ports() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user