mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 06:20:28 +00:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37efc4237a | ||
|
|
2c92339f95 | ||
|
|
9c46a2d55a | ||
|
|
19186edfa1 | ||
|
|
f6a7e93923 | ||
|
|
077070dbe0 | ||
|
|
c3af657c0e | ||
|
|
9a04eecaf9 | ||
|
|
1951a278ac | ||
|
|
f1aee0b7c5 | ||
|
|
5173e5c15d | ||
|
|
daf9cba29f | ||
|
|
445c0d456c | ||
|
|
0a42dba13e | ||
|
|
cd8aab9544 | ||
|
|
59dc2cee2e | ||
|
|
5846f94784 | ||
|
|
e0ee2350fc | ||
|
|
48164c8267 | ||
|
|
2d2102f654 | ||
|
|
262770564f | ||
|
|
777e31302c | ||
|
|
30d6a5221b | ||
|
|
a778d3d273 | ||
|
|
e813a3744f | ||
|
|
66628943d8 | ||
|
|
7ff06f65ec | ||
|
|
1cf5662949 | ||
|
|
a6aca101d6 | ||
|
|
5836f36f69 | ||
|
|
6d6c045a5a | ||
|
|
cfc78b3ac1 | ||
|
|
5f7474120f | ||
|
|
09f619d67c | ||
|
|
760223ad70 | ||
|
|
d75b33a3a3 | ||
|
|
7c418486c8 | ||
|
|
a384be0f84 | ||
|
|
649e989fa2 | ||
|
|
0443de7798 | ||
|
|
9a121a489b | ||
|
|
4bdf6e5c92 | ||
|
|
ef2a967f12 | ||
|
|
92ada2dd1d | ||
|
|
8a9dbd407f | ||
|
|
de6be7c5a9 | ||
|
|
e742e84ded | ||
|
|
7726fbece0 | ||
|
|
0c09f4342b | ||
|
|
14e171ac8e |
@@ -95,11 +95,12 @@
|
||||
## GUI Clients
|
||||
|
||||
- OpenWrt
|
||||
- [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2)
|
||||
- [PassWall](https://github.com/Openwrt-Passwall/openwrt-passwall), [PassWall 2](https://github.com/Openwrt-Passwall/openwrt-passwall2)
|
||||
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
|
||||
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||
- Asuswrt-Merlin
|
||||
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
|
||||
- [fancyss](https://github.com/hq450/fancyss)
|
||||
- Windows
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
@@ -112,11 +113,11 @@
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- macOS arm64 & x64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
|
||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
@@ -137,6 +138,8 @@
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
|
||||
- [Quantumult X](https://apps.apple.com/us/app/quantumult-x/id1443988620)
|
||||
- Xray Tools
|
||||
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
|
||||
- [xray-checker](https://github.com/kutovoys/xray-checker)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
If you found an issue related to security vulnerability or protocol-identification problem, please report it to us via "[Report a vulnerability](https://github.com/XTLS/Xray-core/security/advisories/new)" privately, instead of publish it publicly before we release the fixed version.
|
||||
|
||||
Thanks for your contribution to the FREE Internet!
|
||||
@@ -12,15 +12,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
router "github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// DNS is a DNS rely server.
|
||||
@@ -100,25 +97,6 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
}
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
err := parseDomains(ns)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns domain rules: ").Base(err)
|
||||
}
|
||||
|
||||
expectedGeoip, err := router.GetGeoIPList(ns.ExpectedGeoip)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns expectIPs rules: ").Base(err)
|
||||
}
|
||||
ns.ExpectedGeoip = expectedGeoip
|
||||
|
||||
unexpectedGeoip, err := router.GetGeoIPList(ns.UnexpectedGeoip)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse dns unexpectedGeoip rules: ").Base(err)
|
||||
}
|
||||
ns.UnexpectedGeoip = unexpectedGeoip
|
||||
|
||||
}
|
||||
domainRuleCount += len(ns.PrioritizedDomain)
|
||||
}
|
||||
|
||||
@@ -602,76 +580,3 @@ func detectGUIPlatform() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseDomains(ns *NameServer) error {
|
||||
pureDomains := []*router.Domain{}
|
||||
|
||||
// convert to pure domain
|
||||
for _, pd := range ns.PrioritizedDomain {
|
||||
pureDomains = append(pureDomains, &router.Domain{
|
||||
Type: router.Domain_Type(pd.Type),
|
||||
Value: pd.Domain,
|
||||
})
|
||||
}
|
||||
|
||||
domainList := []*router.Domain{}
|
||||
for _, domain := range pureDomains {
|
||||
val := strings.Split(domain.Value, "_")
|
||||
if len(val) >= 2 {
|
||||
|
||||
fileName := val[0]
|
||||
code := val[1]
|
||||
|
||||
bs, err := filesystem.ReadAsset(fileName)
|
||||
if err != nil {
|
||||
return errors.New("failed to load file: ", fileName).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(code))
|
||||
var geosite router.GeoSite
|
||||
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return errors.New("failed Unmarshal :").Base(err)
|
||||
}
|
||||
|
||||
// parse attr
|
||||
if len(val) == 3 {
|
||||
siteWithAttr := strings.Split(val[2], ",")
|
||||
attrs := router.ParseAttrs(siteWithAttr)
|
||||
if !attrs.IsEmpty() {
|
||||
filteredDomains := make([]*router.Domain, 0, len(pureDomains))
|
||||
for _, domain := range geosite.Domain {
|
||||
if attrs.Match(domain) {
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
geosite.Domain = filteredDomains
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
domainList = append(domainList, geosite.Domain...)
|
||||
|
||||
// update ns.OriginalRules Size
|
||||
ruleTag := strings.Join(val, ":")
|
||||
for i, oRule := range ns.OriginalRules {
|
||||
if oRule.Rule == strings.ToLower(ruleTag) {
|
||||
ns.OriginalRules[i].Size = uint32(len(geosite.Domain))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
domainList = append(domainList, domain)
|
||||
}
|
||||
}
|
||||
|
||||
// convert back to NameServer_PriorityDomain
|
||||
ns.PrioritizedDomain = []*NameServer_PriorityDomain{}
|
||||
for _, pd := range domainList {
|
||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, &NameServer_PriorityDomain{
|
||||
Type: ToDomainMatchingType(pd.Type),
|
||||
Domain: pd.Value,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-4
@@ -541,7 +541,7 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
// local
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
// inner ip, will not match
|
||||
@@ -565,7 +565,7 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
// test
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
@@ -574,7 +574,7 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
// test
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 4},
|
||||
@@ -669,7 +669,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match localhost, localhost-a and localhost-b,
|
||||
// local
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||
|
||||
+4
-1
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -24,7 +25,9 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
matchers: g,
|
||||
}
|
||||
|
||||
for _, mapping := range hosts {
|
||||
defer runtime.GC()
|
||||
for i, mapping := range hosts {
|
||||
hosts[i] = nil
|
||||
matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
|
||||
+9
-16
@@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -131,7 +132,8 @@ func NewClient(
|
||||
var rules []string
|
||||
ruleCurr := 0
|
||||
ruleIter := 0
|
||||
for _, domain := range ns.PrioritizedDomain {
|
||||
for i, domain := range ns.PrioritizedDomain {
|
||||
ns.PrioritizedDomain[i] = nil
|
||||
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
|
||||
@@ -154,6 +156,8 @@ func NewClient(
|
||||
}
|
||||
updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||
}
|
||||
ns.PrioritizedDomain = nil
|
||||
runtime.GC()
|
||||
|
||||
// Establish expected IPs
|
||||
var expectedMatcher router.GeoIPMatcher
|
||||
@@ -162,6 +166,8 @@ func NewClient(
|
||||
if err != nil {
|
||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.ExpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Establish unexpected IPs
|
||||
@@ -171,6 +177,8 @@ func NewClient(
|
||||
if err != nil {
|
||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.UnexpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
@@ -297,18 +305,3 @@ func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption)
|
||||
return ipOption
|
||||
}
|
||||
}
|
||||
|
||||
func ToDomainMatchingType(t router.Domain_Type) DomainMatchingType {
|
||||
switch t {
|
||||
case router.Domain_Domain:
|
||||
return DomainMatchingType_Subdomain
|
||||
case router.Domain_Full:
|
||||
return DomainMatchingType_Full
|
||||
case router.Domain_Plain:
|
||||
return DomainMatchingType_Keyword
|
||||
case router.Domain_Regex:
|
||||
return DomainMatchingType_Regex
|
||||
default:
|
||||
panic("unknown domain type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
|
||||
+83
-33
@@ -2,8 +2,9 @@ package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -20,14 +21,23 @@ type Instance struct {
|
||||
errorLogger log.Handler
|
||||
active bool
|
||||
dns bool
|
||||
mask4 int
|
||||
mask6 int
|
||||
}
|
||||
|
||||
// New creates a new log.Instance based on the given config.
|
||||
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
m4, m6, err := ParseMaskAddress(config.MaskAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g := &Instance{
|
||||
config: config,
|
||||
active: false,
|
||||
dns: config.EnableDnsLog,
|
||||
mask4: m4,
|
||||
mask6: m6,
|
||||
}
|
||||
log.RegisterHandler(g)
|
||||
|
||||
@@ -104,7 +114,11 @@ func (g *Instance) Handle(msg log.Message) {
|
||||
|
||||
var Msg log.Message
|
||||
if g.config.MaskAddress != "" {
|
||||
Msg = &MaskedMsgWrapper{Message: msg, config: g.config}
|
||||
Msg = &MaskedMsgWrapper{
|
||||
Message: msg,
|
||||
Mask4: g.mask4,
|
||||
Mask6: g.mask6,
|
||||
}
|
||||
} else {
|
||||
Msg = msg
|
||||
}
|
||||
@@ -149,51 +163,87 @@ func (g *Instance) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseMaskAddress(c string) (int, int, error) {
|
||||
var m4, m6 int
|
||||
switch c {
|
||||
case "half":
|
||||
m4, m6 = 16, 32
|
||||
case "quarter":
|
||||
m4, m6 = 8, 16
|
||||
case "full":
|
||||
m4, m6 = 0, 0
|
||||
case "":
|
||||
// do nothing
|
||||
default:
|
||||
if parts := strings.Split(c, "+"); len(parts) > 0 {
|
||||
if len(parts) >= 1 && parts[0] != "" {
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(parts[0], "/"))
|
||||
if err != nil {
|
||||
return 32, 128, err
|
||||
}
|
||||
m4 = i
|
||||
}
|
||||
if len(parts) >= 2 && parts[1] != "" {
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(parts[1], "/"))
|
||||
if err != nil {
|
||||
return 32, 128, err
|
||||
}
|
||||
m6 = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m4%8 != 0 || m4 > 32 || m4 < 0 {
|
||||
return 32, 128, errors.New("Log Mask: ipv4 mask must be divisible by 8 and between 0-32")
|
||||
}
|
||||
|
||||
return m4, m6, nil
|
||||
}
|
||||
|
||||
// MaskedMsgWrapper is to wrap the string() method to mask IP addresses in the log.
|
||||
type MaskedMsgWrapper struct {
|
||||
log.Message
|
||||
config *Config
|
||||
Mask4 int
|
||||
Mask6 int
|
||||
}
|
||||
|
||||
var (
|
||||
ipv4Regex = regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}`)
|
||||
ipv6Regex = regexp.MustCompile(`(?:[\da-fA-F]{0,4}:[\da-fA-F]{0,4}){2,7}`)
|
||||
)
|
||||
|
||||
func (m *MaskedMsgWrapper) String() string {
|
||||
str := m.Message.String()
|
||||
|
||||
ipv4Regex := regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}`)
|
||||
ipv6Regex := regexp.MustCompile(`((?:[\da-fA-F]{0,4}:[\da-fA-F]{0,4}){2,7})(?:[\/\\%](\d{1,3}))?`)
|
||||
|
||||
// Process ipv4
|
||||
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(ip string) string {
|
||||
parts := strings.Split(ip, ".")
|
||||
switch m.config.MaskAddress {
|
||||
case "half":
|
||||
return fmt.Sprintf("%s.%s.*.*", parts[0], parts[1])
|
||||
case "quarter":
|
||||
return fmt.Sprintf("%s.*.*.*", parts[0])
|
||||
case "full":
|
||||
return "[Masked IPv4]"
|
||||
default:
|
||||
return ip
|
||||
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(s string) string {
|
||||
if m.Mask4 == 32 {
|
||||
return s
|
||||
}
|
||||
if m.Mask4 == 0 {
|
||||
return "[Masked IPv4]"
|
||||
}
|
||||
|
||||
parts := strings.Split(s, ".")
|
||||
for i := m.Mask4 / 8; i < 4; i++ {
|
||||
parts[i] = "*"
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
})
|
||||
|
||||
// process ipv6
|
||||
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(ip string) string {
|
||||
parts := strings.Split(ip, ":")
|
||||
switch m.config.MaskAddress {
|
||||
case "half":
|
||||
if len(parts) >= 2 {
|
||||
return fmt.Sprintf("%s:%s::/32", parts[0], parts[1])
|
||||
}
|
||||
case "quarter":
|
||||
if len(parts) >= 1 {
|
||||
return fmt.Sprintf("%s::/16", parts[0])
|
||||
}
|
||||
case "full":
|
||||
return "Masked IPv6" // Do not use [Masked IPv6] like ipv4, or you will get "[[Masked IPv6]]" (v6 address already has [])
|
||||
default:
|
||||
return ip
|
||||
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(s string) string {
|
||||
if m.Mask6 == 128 {
|
||||
return s
|
||||
}
|
||||
return ip
|
||||
if m.Mask6 == 0 {
|
||||
return "Masked IPv6"
|
||||
}
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
return s
|
||||
}
|
||||
return ip.Mask(net.CIDRMask(m.Mask6, 128)).String() + "/" + strconv.Itoa(m.Mask6)
|
||||
})
|
||||
|
||||
return maskedMsg
|
||||
|
||||
@@ -2,6 +2,7 @@ package log_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
@@ -50,3 +51,39 @@ func TestCustomLogHandler(t *testing.T) {
|
||||
|
||||
common.Must(logger.Close())
|
||||
}
|
||||
|
||||
func TestMaskAddress(t *testing.T) {
|
||||
m4, m6, err := log.ParseMaskAddress("half")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
maskedAddr := log.MaskedMsgWrapper{
|
||||
Mask4: m4,
|
||||
Mask6: m6,
|
||||
}
|
||||
maskedAddr.Message = net.ParseIP("11.45.1.4")
|
||||
if maskedAddr.String() != "11.45.*.*" {
|
||||
t.Fatal("expected '11.45.*.*', but actually ", maskedAddr.String())
|
||||
}
|
||||
maskedAddr.Message = net.ParseIP("11:45:14:19:19:81:0::")
|
||||
if maskedAddr.String() != "11:45::/32" {
|
||||
t.Fatal("expected '11:45::/32', but actually", maskedAddr.String())
|
||||
}
|
||||
|
||||
m4, m6, err = log.ParseMaskAddress("/16+/64")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
maskedAddr = log.MaskedMsgWrapper{
|
||||
Mask4: m4,
|
||||
Mask6: m6,
|
||||
}
|
||||
maskedAddr.Message = net.ParseIP("11.45.1.4")
|
||||
if maskedAddr.String() != "11.45.*.*" {
|
||||
t.Fatal("expected '11.45.*.*', but actually ", maskedAddr.String())
|
||||
}
|
||||
maskedAddr.Message = net.ParseIP("11:45:14:19:19:81:0::")
|
||||
if maskedAddr.String() != "11:45:14:19::/64" {
|
||||
t.Fatal("expected '11:45:14:19::/64', but actually", maskedAddr.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/mux"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
@@ -52,6 +53,20 @@ type AlwaysOnInboundHandler struct {
|
||||
}
|
||||
|
||||
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
|
||||
// Set tag and sniffing config in context before creating proxy
|
||||
// This allows proxies like TUN to access these settings
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
|
||||
if receiverConfig.SniffingSettings != nil {
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: session.SniffingRequest{
|
||||
Enabled: receiverConfig.SniffingSettings.Enabled,
|
||||
OverrideDestinationForProtocol: receiverConfig.SniffingSettings.DestinationOverride,
|
||||
ExcludeForDomain: receiverConfig.SniffingSettings.DomainsExcluded,
|
||||
MetadataOnly: receiverConfig.SniffingSettings.MetadataOnly,
|
||||
RouteOnly: receiverConfig.SniffingSettings.RouteOnly,
|
||||
},
|
||||
})
|
||||
}
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -60,6 +60,7 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
|
||||
return nil, errors.New("unsupported router implementation")
|
||||
|
||||
}
|
||||
|
||||
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||
if bo, ok := s.router.(routing.Router); ok {
|
||||
return &RemoveRuleResponse{}, bo.RemoveRule(request.RuleTag)
|
||||
@@ -67,6 +68,20 @@ func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleReque
|
||||
return nil, errors.New("unsupported router implementation")
|
||||
}
|
||||
|
||||
func (s *routingServer) ListRule(ctx context.Context, request *ListRuleRequest) (*ListRuleResponse, error) {
|
||||
if bo, ok := s.router.(routing.Router); ok {
|
||||
response := &ListRuleResponse{}
|
||||
for _, v := range bo.ListRule() {
|
||||
response.Rules = append(response.Rules, &ListRuleItem{
|
||||
Tag: v.GetOutboundTag(),
|
||||
RuleTag: v.GetRuleTag(),
|
||||
})
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
return nil, errors.New("unsupported router implementation")
|
||||
}
|
||||
|
||||
// NewRoutingServer creates a statistics service with statistics manager.
|
||||
func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer {
|
||||
return &routingServer{
|
||||
|
||||
+316
-273
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.2
|
||||
// source: app/router/command/command.proto
|
||||
|
||||
package command
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,25 +27,24 @@ const (
|
||||
// It conforms to the structure of xray.features.routing.Context and
|
||||
// xray.features.routing.Route.
|
||||
type RoutingContext struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
InboundTag string `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"`
|
||||
Network net.Network `protobuf:"varint,2,opt,name=Network,proto3,enum=xray.common.net.Network" json:"Network,omitempty"`
|
||||
SourceIPs [][]byte `protobuf:"bytes,3,rep,name=SourceIPs,proto3" json:"SourceIPs,omitempty"`
|
||||
TargetIPs [][]byte `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"`
|
||||
SourcePort uint32 `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"`
|
||||
TargetPort uint32 `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"`
|
||||
TargetDomain string `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,omitempty"`
|
||||
Protocol string `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"`
|
||||
User string `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"`
|
||||
Attributes map[string]string `protobuf:"bytes,10,rep,name=Attributes,proto3" json:"Attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
OutboundGroupTags []string `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"`
|
||||
OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"`
|
||||
LocalIPs [][]byte `protobuf:"bytes,13,rep,name=LocalIPs,proto3" json:"LocalIPs,omitempty"`
|
||||
LocalPort uint32 `protobuf:"varint,14,opt,name=LocalPort,proto3" json:"LocalPort,omitempty"`
|
||||
VlessRoute uint32 `protobuf:"varint,15,opt,name=VlessRoute,proto3" json:"VlessRoute,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
InboundTag string `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"`
|
||||
Network net.Network `protobuf:"varint,2,opt,name=Network,proto3,enum=xray.common.net.Network" json:"Network,omitempty"`
|
||||
SourceIPs [][]byte `protobuf:"bytes,3,rep,name=SourceIPs,proto3" json:"SourceIPs,omitempty"`
|
||||
TargetIPs [][]byte `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"`
|
||||
SourcePort uint32 `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"`
|
||||
TargetPort uint32 `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"`
|
||||
TargetDomain string `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,omitempty"`
|
||||
Protocol string `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"`
|
||||
User string `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"`
|
||||
Attributes map[string]string `protobuf:"bytes,10,rep,name=Attributes,proto3" json:"Attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
OutboundGroupTags []string `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"`
|
||||
OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"`
|
||||
LocalIPs [][]byte `protobuf:"bytes,13,rep,name=LocalIPs,proto3" json:"LocalIPs,omitempty"`
|
||||
LocalPort uint32 `protobuf:"varint,14,opt,name=LocalPort,proto3" json:"LocalPort,omitempty"`
|
||||
VlessRoute uint32 `protobuf:"varint,15,opt,name=VlessRoute,proto3" json:"VlessRoute,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RoutingContext) Reset() {
|
||||
@@ -201,11 +201,10 @@ func (x *RoutingContext) GetVlessRoute() uint32 {
|
||||
//
|
||||
// * If FieldSelectors is left empty, all fields will be returned.
|
||||
type SubscribeRoutingStatsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SubscribeRoutingStatsRequest) Reset() {
|
||||
@@ -253,13 +252,12 @@ func (x *SubscribeRoutingStatsRequest) GetFieldSelectors() []string {
|
||||
// * PublishResult broadcasts the routing result to routing statistics channel
|
||||
// if set true.
|
||||
type TestRouteRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"`
|
||||
FieldSelectors []string `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||
PublishResult bool `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"`
|
||||
FieldSelectors []string `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||
PublishResult bool `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TestRouteRequest) Reset() {
|
||||
@@ -314,11 +312,10 @@ func (x *TestRouteRequest) GetPublishResult() bool {
|
||||
}
|
||||
|
||||
type PrincipleTargetInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Tag []string `protobuf:"bytes,1,rep,name=tag,proto3" json:"tag,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Tag []string `protobuf:"bytes,1,rep,name=tag,proto3" json:"tag,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PrincipleTargetInfo) Reset() {
|
||||
@@ -359,11 +356,10 @@ func (x *PrincipleTargetInfo) GetTag() []string {
|
||||
}
|
||||
|
||||
type OverrideInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *OverrideInfo) Reset() {
|
||||
@@ -404,12 +400,11 @@ func (x *OverrideInfo) GetTarget() string {
|
||||
}
|
||||
|
||||
type BalancerMsg struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Override *OverrideInfo `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"`
|
||||
PrincipleTarget *PrincipleTargetInfo `protobuf:"bytes,6,opt,name=principle_target,json=principleTarget,proto3" json:"principle_target,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Override *OverrideInfo `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"`
|
||||
PrincipleTarget *PrincipleTargetInfo `protobuf:"bytes,6,opt,name=principle_target,json=principleTarget,proto3" json:"principle_target,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *BalancerMsg) Reset() {
|
||||
@@ -457,11 +452,10 @@ func (x *BalancerMsg) GetPrincipleTarget() *PrincipleTargetInfo {
|
||||
}
|
||||
|
||||
type GetBalancerInfoRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetBalancerInfoRequest) Reset() {
|
||||
@@ -502,11 +496,10 @@ func (x *GetBalancerInfoRequest) GetTag() string {
|
||||
}
|
||||
|
||||
type GetBalancerInfoResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Balancer *BalancerMsg `protobuf:"bytes,1,opt,name=balancer,proto3" json:"balancer,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Balancer *BalancerMsg `protobuf:"bytes,1,opt,name=balancer,proto3" json:"balancer,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetBalancerInfoResponse) Reset() {
|
||||
@@ -547,12 +540,11 @@ func (x *GetBalancerInfoResponse) GetBalancer() *BalancerMsg {
|
||||
}
|
||||
|
||||
type OverrideBalancerTargetRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
BalancerTag string `protobuf:"bytes,1,opt,name=balancerTag,proto3" json:"balancerTag,omitempty"`
|
||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
BalancerTag string `protobuf:"bytes,1,opt,name=balancerTag,proto3" json:"balancerTag,omitempty"`
|
||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *OverrideBalancerTargetRequest) Reset() {
|
||||
@@ -600,9 +592,9 @@ func (x *OverrideBalancerTargetRequest) GetTarget() string {
|
||||
}
|
||||
|
||||
type OverrideBalancerTargetResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *OverrideBalancerTargetResponse) Reset() {
|
||||
@@ -636,12 +628,11 @@ func (*OverrideBalancerTargetResponse) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type AddRuleRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Config *serial.TypedMessage `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
||||
ShouldAppend bool `protobuf:"varint,2,opt,name=shouldAppend,proto3" json:"shouldAppend,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Config *serial.TypedMessage `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
||||
ShouldAppend bool `protobuf:"varint,2,opt,name=shouldAppend,proto3" json:"shouldAppend,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AddRuleRequest) Reset() {
|
||||
@@ -689,9 +680,9 @@ func (x *AddRuleRequest) GetShouldAppend() bool {
|
||||
}
|
||||
|
||||
type AddRuleResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AddRuleResponse) Reset() {
|
||||
@@ -725,11 +716,10 @@ func (*AddRuleResponse) Descriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type RemoveRuleRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RuleTag string `protobuf:"bytes,1,opt,name=ruleTag,proto3" json:"ruleTag,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RuleTag string `protobuf:"bytes,1,opt,name=ruleTag,proto3" json:"ruleTag,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RemoveRuleRequest) Reset() {
|
||||
@@ -770,9 +760,9 @@ func (x *RemoveRuleRequest) GetRuleTag() string {
|
||||
}
|
||||
|
||||
type RemoveRuleResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RemoveRuleResponse) Reset() {
|
||||
@@ -805,15 +795,147 @@ func (*RemoveRuleResponse) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
type ListRuleRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListRuleRequest) Reset() {
|
||||
*x = ListRuleRequest{}
|
||||
mi := &file_app_router_command_command_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListRuleRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListRuleRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListRuleRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_command_command_proto_msgTypes[14]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListRuleRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListRuleRequest) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
type ListRuleItem struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
RuleTag string `protobuf:"bytes,2,opt,name=ruleTag,proto3" json:"ruleTag,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListRuleItem) Reset() {
|
||||
*x = ListRuleItem{}
|
||||
mi := &file_app_router_command_command_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListRuleItem) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListRuleItem) ProtoMessage() {}
|
||||
|
||||
func (x *ListRuleItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_command_command_proto_msgTypes[15]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListRuleItem.ProtoReflect.Descriptor instead.
|
||||
func (*ListRuleItem) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
func (x *ListRuleItem) GetTag() string {
|
||||
if x != nil {
|
||||
return x.Tag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListRuleItem) GetRuleTag() string {
|
||||
if x != nil {
|
||||
return x.RuleTag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListRuleResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Rules []*ListRuleItem `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListRuleResponse) Reset() {
|
||||
*x = ListRuleResponse{}
|
||||
mi := &file_app_router_command_command_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListRuleResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListRuleResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListRuleResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_command_command_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListRuleResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListRuleResponse) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *ListRuleResponse) GetRules() []*ListRuleItem {
|
||||
if x != nil {
|
||||
return x.Rules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_router_command_command_proto_msgTypes[14]
|
||||
mi := &file_app_router_command_command_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -825,7 +947,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_command_command_proto_msgTypes[14]
|
||||
mi := &file_app_router_command_command_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -838,187 +960,103 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{14}
|
||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
var File_app_router_command_command_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_app_router_command_command_proto_rawDesc = []byte{
|
||||
0x0a, 0x20, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x17, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x18, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65,
|
||||
0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf6, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75,
|
||||
0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x49,
|
||||
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x4e,
|
||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78,
|
||||
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e,
|
||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||
0x28, 0x0c, 0x52, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a,
|
||||
0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c,
|
||||
0x52, 0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x53,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x54,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x54,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55,
|
||||
0x73, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12,
|
||||
0x57, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x74, 0x74,
|
||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x41, 0x74,
|
||||
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x4f, 0x75, 0x74, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f,
|
||||
0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75,
|
||||
0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x75, 0x74,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61,
|
||||
0x6c, 0x49, 0x50, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x4c, 0x6f, 0x63, 0x61,
|
||||
0x6c, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f, 0x72,
|
||||
0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f,
|
||||
0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75,
|
||||
0x74, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x22, 0x46, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64,
|
||||
0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xb1, 0x01, 0x0a, 0x10, 0x54, 0x65,
|
||||
0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f,
|
||||
0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||
0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52,
|
||||
0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12,
|
||||
0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65,
|
||||
0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69,
|
||||
0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
|
||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x27, 0x0a,
|
||||
0x13, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
|
||||
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x26, 0x0a, 0x0c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
|
||||
0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xa9,
|
||||
0x01, 0x0a, 0x0b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x41,
|
||||
0x0a, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65, 0x72, 0x72,
|
||||
0x69, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
||||
0x65, 0x12, 0x57, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6e, 0x63,
|
||||
0x69, 0x70, 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x2a, 0x0a, 0x16, 0x47, 0x65,
|
||||
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x5b, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c,
|
||||
0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x40, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x42, 0x61,
|
||||
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x52, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x1d, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42,
|
||||
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72,
|
||||
0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x61, 0x6c, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x72, 0x54, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x20,
|
||||
0x0a, 0x1e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
|
||||
0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x6e, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x22, 0x0a, 0x0c,
|
||||
0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x41, 0x70, 0x70, 0x65, 0x6e, 0x64,
|
||||
0x22, 0x11, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x75, 0x6c,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65,
|
||||
0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x54,
|
||||
0x61, 0x67, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x75, 0x6c, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x32, 0xbf, 0x05, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x35,
|
||||
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00,
|
||||
0x30, 0x01, 0x12, 0x61, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12,
|
||||
0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x78, 0x74, 0x22, 0x00, 0x12, 0x76, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61,
|
||||
0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||
0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49,
|
||||
0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x8b, 0x01,
|
||||
0x0a, 0x16, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
|
||||
0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x36, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||
0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x37, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
||||
0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65, 0x72, 0x72,
|
||||
0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x07, 0x41,
|
||||
0x64, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
||||
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||
0x2e, 0x41, 0x64, 0x64, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||
0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x75, 0x6c,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x0a, 0x52,
|
||||
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
|
||||
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e,
|
||||
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x61, 0x6e, 0x64, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_app_router_command_command_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
" app/router/command/command.proto\x12\x17xray.app.router.command\x1a\x18common/net/network.proto\x1a!common/serial/typed_message.proto\"\xf6\x04\n" +
|
||||
"\x0eRoutingContext\x12\x1e\n" +
|
||||
"\n" +
|
||||
"InboundTag\x18\x01 \x01(\tR\n" +
|
||||
"InboundTag\x122\n" +
|
||||
"\aNetwork\x18\x02 \x01(\x0e2\x18.xray.common.net.NetworkR\aNetwork\x12\x1c\n" +
|
||||
"\tSourceIPs\x18\x03 \x03(\fR\tSourceIPs\x12\x1c\n" +
|
||||
"\tTargetIPs\x18\x04 \x03(\fR\tTargetIPs\x12\x1e\n" +
|
||||
"\n" +
|
||||
"SourcePort\x18\x05 \x01(\rR\n" +
|
||||
"SourcePort\x12\x1e\n" +
|
||||
"\n" +
|
||||
"TargetPort\x18\x06 \x01(\rR\n" +
|
||||
"TargetPort\x12\"\n" +
|
||||
"\fTargetDomain\x18\a \x01(\tR\fTargetDomain\x12\x1a\n" +
|
||||
"\bProtocol\x18\b \x01(\tR\bProtocol\x12\x12\n" +
|
||||
"\x04User\x18\t \x01(\tR\x04User\x12W\n" +
|
||||
"\n" +
|
||||
"Attributes\x18\n" +
|
||||
" \x03(\v27.xray.app.router.command.RoutingContext.AttributesEntryR\n" +
|
||||
"Attributes\x12,\n" +
|
||||
"\x11OutboundGroupTags\x18\v \x03(\tR\x11OutboundGroupTags\x12 \n" +
|
||||
"\vOutboundTag\x18\f \x01(\tR\vOutboundTag\x12\x1a\n" +
|
||||
"\bLocalIPs\x18\r \x03(\fR\bLocalIPs\x12\x1c\n" +
|
||||
"\tLocalPort\x18\x0e \x01(\rR\tLocalPort\x12\x1e\n" +
|
||||
"\n" +
|
||||
"VlessRoute\x18\x0f \x01(\rR\n" +
|
||||
"VlessRoute\x1a=\n" +
|
||||
"\x0fAttributesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"F\n" +
|
||||
"\x1cSubscribeRoutingStatsRequest\x12&\n" +
|
||||
"\x0eFieldSelectors\x18\x01 \x03(\tR\x0eFieldSelectors\"\xb1\x01\n" +
|
||||
"\x10TestRouteRequest\x12O\n" +
|
||||
"\x0eRoutingContext\x18\x01 \x01(\v2'.xray.app.router.command.RoutingContextR\x0eRoutingContext\x12&\n" +
|
||||
"\x0eFieldSelectors\x18\x02 \x03(\tR\x0eFieldSelectors\x12$\n" +
|
||||
"\rPublishResult\x18\x03 \x01(\bR\rPublishResult\"'\n" +
|
||||
"\x13PrincipleTargetInfo\x12\x10\n" +
|
||||
"\x03tag\x18\x01 \x03(\tR\x03tag\"&\n" +
|
||||
"\fOverrideInfo\x12\x16\n" +
|
||||
"\x06target\x18\x02 \x01(\tR\x06target\"\xa9\x01\n" +
|
||||
"\vBalancerMsg\x12A\n" +
|
||||
"\boverride\x18\x05 \x01(\v2%.xray.app.router.command.OverrideInfoR\boverride\x12W\n" +
|
||||
"\x10principle_target\x18\x06 \x01(\v2,.xray.app.router.command.PrincipleTargetInfoR\x0fprincipleTarget\"*\n" +
|
||||
"\x16GetBalancerInfoRequest\x12\x10\n" +
|
||||
"\x03tag\x18\x01 \x01(\tR\x03tag\"[\n" +
|
||||
"\x17GetBalancerInfoResponse\x12@\n" +
|
||||
"\bbalancer\x18\x01 \x01(\v2$.xray.app.router.command.BalancerMsgR\bbalancer\"Y\n" +
|
||||
"\x1dOverrideBalancerTargetRequest\x12 \n" +
|
||||
"\vbalancerTag\x18\x01 \x01(\tR\vbalancerTag\x12\x16\n" +
|
||||
"\x06target\x18\x02 \x01(\tR\x06target\" \n" +
|
||||
"\x1eOverrideBalancerTargetResponse\"n\n" +
|
||||
"\x0eAddRuleRequest\x128\n" +
|
||||
"\x06config\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\x06config\x12\"\n" +
|
||||
"\fshouldAppend\x18\x02 \x01(\bR\fshouldAppend\"\x11\n" +
|
||||
"\x0fAddRuleResponse\"-\n" +
|
||||
"\x11RemoveRuleRequest\x12\x18\n" +
|
||||
"\aruleTag\x18\x01 \x01(\tR\aruleTag\"\x14\n" +
|
||||
"\x12RemoveRuleResponse\"\x11\n" +
|
||||
"\x0fListRuleRequest\":\n" +
|
||||
"\fListRuleItem\x12\x10\n" +
|
||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x12\x18\n" +
|
||||
"\aruleTag\x18\x02 \x01(\tR\aruleTag\"O\n" +
|
||||
"\x10ListRuleResponse\x12;\n" +
|
||||
"\x05rules\x18\x01 \x03(\v2%.xray.app.router.command.ListRuleItemR\x05rules\"\b\n" +
|
||||
"\x06Config2\xa2\x06\n" +
|
||||
"\x0eRoutingService\x12{\n" +
|
||||
"\x15SubscribeRoutingStats\x125.xray.app.router.command.SubscribeRoutingStatsRequest\x1a'.xray.app.router.command.RoutingContext\"\x000\x01\x12a\n" +
|
||||
"\tTestRoute\x12).xray.app.router.command.TestRouteRequest\x1a'.xray.app.router.command.RoutingContext\"\x00\x12v\n" +
|
||||
"\x0fGetBalancerInfo\x12/.xray.app.router.command.GetBalancerInfoRequest\x1a0.xray.app.router.command.GetBalancerInfoResponse\"\x00\x12\x8b\x01\n" +
|
||||
"\x16OverrideBalancerTarget\x126.xray.app.router.command.OverrideBalancerTargetRequest\x1a7.xray.app.router.command.OverrideBalancerTargetResponse\"\x00\x12^\n" +
|
||||
"\aAddRule\x12'.xray.app.router.command.AddRuleRequest\x1a(.xray.app.router.command.AddRuleResponse\"\x00\x12g\n" +
|
||||
"\n" +
|
||||
"RemoveRule\x12*.xray.app.router.command.RemoveRuleRequest\x1a+.xray.app.router.command.RemoveRuleResponse\"\x00\x12a\n" +
|
||||
"\bListRule\x12(.xray.app.router.command.ListRuleRequest\x1a).xray.app.router.command.ListRuleResponse\"\x00Bg\n" +
|
||||
"\x1bcom.xray.app.router.commandP\x01Z,github.com/xtls/xray-core/app/router/command\xaa\x02\x17Xray.App.Router.Commandb\x06proto3"
|
||||
|
||||
var (
|
||||
file_app_router_command_command_proto_rawDescOnce sync.Once
|
||||
file_app_router_command_command_proto_rawDescData = file_app_router_command_command_proto_rawDesc
|
||||
file_app_router_command_command_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_app_router_command_command_proto_rawDescGZIP() []byte {
|
||||
file_app_router_command_command_proto_rawDescOnce.Do(func() {
|
||||
file_app_router_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_router_command_command_proto_rawDescData)
|
||||
file_app_router_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_router_command_command_proto_rawDesc), len(file_app_router_command_command_proto_rawDesc)))
|
||||
})
|
||||
return file_app_router_command_command_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
|
||||
var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
||||
var file_app_router_command_command_proto_goTypes = []any{
|
||||
(*RoutingContext)(nil), // 0: xray.app.router.command.RoutingContext
|
||||
(*SubscribeRoutingStatsRequest)(nil), // 1: xray.app.router.command.SubscribeRoutingStatsRequest
|
||||
@@ -1034,36 +1072,42 @@ var file_app_router_command_command_proto_goTypes = []any{
|
||||
(*AddRuleResponse)(nil), // 11: xray.app.router.command.AddRuleResponse
|
||||
(*RemoveRuleRequest)(nil), // 12: xray.app.router.command.RemoveRuleRequest
|
||||
(*RemoveRuleResponse)(nil), // 13: xray.app.router.command.RemoveRuleResponse
|
||||
(*Config)(nil), // 14: xray.app.router.command.Config
|
||||
nil, // 15: xray.app.router.command.RoutingContext.AttributesEntry
|
||||
(net.Network)(0), // 16: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 17: xray.common.serial.TypedMessage
|
||||
(*ListRuleRequest)(nil), // 14: xray.app.router.command.ListRuleRequest
|
||||
(*ListRuleItem)(nil), // 15: xray.app.router.command.ListRuleItem
|
||||
(*ListRuleResponse)(nil), // 16: xray.app.router.command.ListRuleResponse
|
||||
(*Config)(nil), // 17: xray.app.router.command.Config
|
||||
nil, // 18: xray.app.router.command.RoutingContext.AttributesEntry
|
||||
(net.Network)(0), // 19: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 20: xray.common.serial.TypedMessage
|
||||
}
|
||||
var file_app_router_command_command_proto_depIdxs = []int32{
|
||||
16, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network
|
||||
15, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry
|
||||
19, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network
|
||||
18, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry
|
||||
0, // 2: xray.app.router.command.TestRouteRequest.RoutingContext:type_name -> xray.app.router.command.RoutingContext
|
||||
4, // 3: xray.app.router.command.BalancerMsg.override:type_name -> xray.app.router.command.OverrideInfo
|
||||
3, // 4: xray.app.router.command.BalancerMsg.principle_target:type_name -> xray.app.router.command.PrincipleTargetInfo
|
||||
5, // 5: xray.app.router.command.GetBalancerInfoResponse.balancer:type_name -> xray.app.router.command.BalancerMsg
|
||||
17, // 6: xray.app.router.command.AddRuleRequest.config:type_name -> xray.common.serial.TypedMessage
|
||||
1, // 7: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest
|
||||
2, // 8: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest
|
||||
6, // 9: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest
|
||||
8, // 10: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest
|
||||
10, // 11: xray.app.router.command.RoutingService.AddRule:input_type -> xray.app.router.command.AddRuleRequest
|
||||
12, // 12: xray.app.router.command.RoutingService.RemoveRule:input_type -> xray.app.router.command.RemoveRuleRequest
|
||||
0, // 13: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext
|
||||
0, // 14: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext
|
||||
7, // 15: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse
|
||||
9, // 16: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse
|
||||
11, // 17: xray.app.router.command.RoutingService.AddRule:output_type -> xray.app.router.command.AddRuleResponse
|
||||
13, // 18: xray.app.router.command.RoutingService.RemoveRule:output_type -> xray.app.router.command.RemoveRuleResponse
|
||||
13, // [13:19] is the sub-list for method output_type
|
||||
7, // [7:13] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
20, // 6: xray.app.router.command.AddRuleRequest.config:type_name -> xray.common.serial.TypedMessage
|
||||
15, // 7: xray.app.router.command.ListRuleResponse.rules:type_name -> xray.app.router.command.ListRuleItem
|
||||
1, // 8: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest
|
||||
2, // 9: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest
|
||||
6, // 10: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest
|
||||
8, // 11: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest
|
||||
10, // 12: xray.app.router.command.RoutingService.AddRule:input_type -> xray.app.router.command.AddRuleRequest
|
||||
12, // 13: xray.app.router.command.RoutingService.RemoveRule:input_type -> xray.app.router.command.RemoveRuleRequest
|
||||
14, // 14: xray.app.router.command.RoutingService.ListRule:input_type -> xray.app.router.command.ListRuleRequest
|
||||
0, // 15: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext
|
||||
0, // 16: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext
|
||||
7, // 17: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse
|
||||
9, // 18: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse
|
||||
11, // 19: xray.app.router.command.RoutingService.AddRule:output_type -> xray.app.router.command.AddRuleResponse
|
||||
13, // 20: xray.app.router.command.RoutingService.RemoveRule:output_type -> xray.app.router.command.RemoveRuleResponse
|
||||
16, // 21: xray.app.router.command.RoutingService.ListRule:output_type -> xray.app.router.command.ListRuleResponse
|
||||
15, // [15:22] is the sub-list for method output_type
|
||||
8, // [8:15] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_router_command_command_proto_init() }
|
||||
@@ -1075,9 +1119,9 @@ func file_app_router_command_command_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_app_router_command_command_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_command_command_proto_rawDesc), len(file_app_router_command_command_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 16,
|
||||
NumMessages: 19,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
@@ -1086,7 +1130,6 @@ func file_app_router_command_command_proto_init() {
|
||||
MessageInfos: file_app_router_command_command_proto_msgTypes,
|
||||
}.Build()
|
||||
File_app_router_command_command_proto = out.File
|
||||
file_app_router_command_command_proto_rawDesc = nil
|
||||
file_app_router_command_command_proto_goTypes = nil
|
||||
file_app_router_command_command_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -104,6 +104,17 @@ message RemoveRuleRequest {
|
||||
|
||||
message RemoveRuleResponse {}
|
||||
|
||||
message ListRuleRequest {}
|
||||
|
||||
message ListRuleItem {
|
||||
string tag = 1;
|
||||
string ruleTag = 2;
|
||||
}
|
||||
|
||||
message ListRuleResponse{
|
||||
repeated ListRuleItem rules = 1;
|
||||
}
|
||||
|
||||
service RoutingService {
|
||||
rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest)
|
||||
returns (stream RoutingContext) {}
|
||||
@@ -114,6 +125,8 @@ service RoutingService {
|
||||
|
||||
rpc AddRule(AddRuleRequest) returns (AddRuleResponse) {}
|
||||
rpc RemoveRule(RemoveRuleRequest) returns (RemoveRuleResponse) {}
|
||||
|
||||
rpc ListRule(ListRuleRequest) returns (ListRuleResponse) {}
|
||||
}
|
||||
|
||||
message Config {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.28.2
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v6.33.2
|
||||
// source: app/router/command/command.proto
|
||||
|
||||
package command
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
RoutingService_OverrideBalancerTarget_FullMethodName = "/xray.app.router.command.RoutingService/OverrideBalancerTarget"
|
||||
RoutingService_AddRule_FullMethodName = "/xray.app.router.command.RoutingService/AddRule"
|
||||
RoutingService_RemoveRule_FullMethodName = "/xray.app.router.command.RoutingService/RemoveRule"
|
||||
RoutingService_ListRule_FullMethodName = "/xray.app.router.command.RoutingService/ListRule"
|
||||
)
|
||||
|
||||
// RoutingServiceClient is the client API for RoutingService service.
|
||||
@@ -37,6 +38,7 @@ type RoutingServiceClient interface {
|
||||
OverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error)
|
||||
AddRule(ctx context.Context, in *AddRuleRequest, opts ...grpc.CallOption) (*AddRuleResponse, error)
|
||||
RemoveRule(ctx context.Context, in *RemoveRuleRequest, opts ...grpc.CallOption) (*RemoveRuleResponse, error)
|
||||
ListRule(ctx context.Context, in *ListRuleRequest, opts ...grpc.CallOption) (*ListRuleResponse, error)
|
||||
}
|
||||
|
||||
type routingServiceClient struct {
|
||||
@@ -116,6 +118,16 @@ func (c *routingServiceClient) RemoveRule(ctx context.Context, in *RemoveRuleReq
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *routingServiceClient) ListRule(ctx context.Context, in *ListRuleRequest, opts ...grpc.CallOption) (*ListRuleResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListRuleResponse)
|
||||
err := c.cc.Invoke(ctx, RoutingService_ListRule_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RoutingServiceServer is the server API for RoutingService service.
|
||||
// All implementations must embed UnimplementedRoutingServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -126,6 +138,7 @@ type RoutingServiceServer interface {
|
||||
OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error)
|
||||
AddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error)
|
||||
RemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error)
|
||||
ListRule(context.Context, *ListRuleRequest) (*ListRuleResponse, error)
|
||||
mustEmbedUnimplementedRoutingServiceServer()
|
||||
}
|
||||
|
||||
@@ -137,22 +150,25 @@ type RoutingServiceServer interface {
|
||||
type UnimplementedRoutingServiceServer struct{}
|
||||
|
||||
func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRoutingStatsRequest, grpc.ServerStreamingServer[RoutingContext]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SubscribeRoutingStats not implemented")
|
||||
return status.Error(codes.Unimplemented, "method SubscribeRoutingStats not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method TestRoute not implemented")
|
||||
return nil, status.Error(codes.Unimplemented, "method TestRoute not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) GetBalancerInfo(context.Context, *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetBalancerInfo not implemented")
|
||||
return nil, status.Error(codes.Unimplemented, "method GetBalancerInfo not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method OverrideBalancerTarget not implemented")
|
||||
return nil, status.Error(codes.Unimplemented, "method OverrideBalancerTarget not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) AddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddRule not implemented")
|
||||
return nil, status.Error(codes.Unimplemented, "method AddRule not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) RemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveRule not implemented")
|
||||
return nil, status.Error(codes.Unimplemented, "method RemoveRule not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) ListRule(context.Context, *ListRuleRequest) (*ListRuleResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListRule not implemented")
|
||||
}
|
||||
func (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {}
|
||||
func (UnimplementedRoutingServiceServer) testEmbeddedByValue() {}
|
||||
@@ -165,7 +181,7 @@ type UnsafeRoutingServiceServer interface {
|
||||
}
|
||||
|
||||
func RegisterRoutingServiceServer(s grpc.ServiceRegistrar, srv RoutingServiceServer) {
|
||||
// If the following call pancis, it indicates UnimplementedRoutingServiceServer was
|
||||
// If the following call panics, it indicates UnimplementedRoutingServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
@@ -276,6 +292,24 @@ func _RoutingService_RemoveRule_Handler(srv interface{}, ctx context.Context, de
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _RoutingService_ListRule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListRuleRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(RoutingServiceServer).ListRule(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: RoutingService_ListRule_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RoutingServiceServer).ListRule(ctx, req.(*ListRuleRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// RoutingService_ServiceDesc is the grpc.ServiceDesc for RoutingService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -303,6 +337,10 @@ var RoutingService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "RemoveRule",
|
||||
Handler: _RoutingService_RemoveRule_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListRule",
|
||||
Handler: _RoutingService_ListRule_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
||||
+6
-44
@@ -57,7 +57,8 @@ type DomainMatcher struct {
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
for i, d := range domains {
|
||||
domains[i] = nil
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
if !f {
|
||||
errors.LogError(context.Background(), "ignore unsupported domain type ", d.Type, " of rule ", d.Value)
|
||||
@@ -309,48 +310,6 @@ func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
|
||||
return m.Match(attributes)
|
||||
}
|
||||
|
||||
// Geo attribute
|
||||
type GeoAttributeMatcher interface {
|
||||
Match(*Domain) bool
|
||||
}
|
||||
|
||||
type GeoBooleanMatcher string
|
||||
|
||||
func (m GeoBooleanMatcher) Match(domain *Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if attr.Key == string(m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoAttributeList struct {
|
||||
Matcher []GeoAttributeMatcher
|
||||
}
|
||||
|
||||
func (al *GeoAttributeList) Match(domain *Domain) bool {
|
||||
for _, matcher := range al.Matcher {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (al *GeoAttributeList) IsEmpty() bool {
|
||||
return len(al.Matcher) == 0
|
||||
}
|
||||
|
||||
func ParseAttrs(attrs []string) *GeoAttributeList {
|
||||
al := new(GeoAttributeList)
|
||||
for _, attr := range attrs {
|
||||
lc := strings.ToLower(attr)
|
||||
al.Matcher = append(al.Matcher, GeoBooleanMatcher(lc))
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
type ProcessNameMatcher struct {
|
||||
ProcessNames []string
|
||||
AbsPaths []string
|
||||
@@ -400,6 +359,9 @@ func NewProcessNameMatcher(names []string) *ProcessNameMatcher {
|
||||
}
|
||||
|
||||
func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(ctx.GetSourceIPs()) == 0 {
|
||||
return false
|
||||
}
|
||||
srcPort := ctx.GetSourcePort().String()
|
||||
srcIP := ctx.GetSourceIPs()[0].String()
|
||||
var network string
|
||||
@@ -439,4 +401,4 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,7 +822,8 @@ func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
|
||||
var ipv4Builder, ipv6Builder netipx.IPSetBuilder
|
||||
|
||||
for _, cidrGroup := range cidrGroups {
|
||||
for _, cidrEntry := range cidrGroup {
|
||||
for i, cidrEntry := range cidrGroup {
|
||||
cidrGroup[i] = nil
|
||||
ipBytes := cidrEntry.GetIp()
|
||||
prefixLen := int(cidrEntry.GetPrefix())
|
||||
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func getAssetPath(file string) (string, error) {
|
||||
path := platform.GetAssetLocation(file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
path := filepath.Join("..", "..", "resources", file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("can't find %s in standard asset locations or {project_root}/resources", file)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||
@@ -159,11 +182,12 @@ func TestGeoIPReverseMatcher(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
geo := "geoip:cn"
|
||||
geoip, err := loadGeoIP(geo)
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||
@@ -172,11 +196,12 @@ func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher6US(t *testing.T) {
|
||||
geo := "geoip:us"
|
||||
geoip, err := loadGeoIP(geo)
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||
@@ -184,34 +209,37 @@ func TestGeoIPMatcher6US(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(geo string) (*router.GeoIP, error) {
|
||||
os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
|
||||
|
||||
geoip, err := conf.ToCidrList([]string{geo})
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
path, err := getAssetPath("geoip.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geoipBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
geoip, err = router.GetGeoIPList(geoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if geoip.CountryCode == country {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(geoip) == 0 {
|
||||
panic("country not found: " + geo)
|
||||
}
|
||||
|
||||
return geoip[0], nil
|
||||
panic("country not found: " + country)
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
geo := "geoip:cn"
|
||||
geoip, err := loadGeoIP(geo)
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
@@ -222,11 +250,12 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||
geo := "geoip:us"
|
||||
geoip, err := loadGeoIP(geo)
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
. "github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func withBackground() routing.Context {
|
||||
@@ -302,25 +300,32 @@ func TestRoutingRule(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoSiteDomains(geo string) ([]*Domain, error) {
|
||||
os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
|
||||
|
||||
domains, err := conf.ParseDomainRule(geo)
|
||||
func loadGeoSite(country string) ([]*Domain, error) {
|
||||
path, err := getAssetPath("geosite.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geositeBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
domains, err = router.GetDomainList(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var geositeList GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, site := range geositeList.Entry {
|
||||
if site.CountryCode == country {
|
||||
return site.Domain, nil
|
||||
}
|
||||
}
|
||||
return domains, nil
|
||||
|
||||
return nil, errors.New("country not found: " + country)
|
||||
}
|
||||
|
||||
func TestChinaSites(t *testing.T) {
|
||||
domains, err := loadGeoSiteDomains("geosite:cn")
|
||||
domains, err := loadGeoSite("CN")
|
||||
common.Must(err)
|
||||
|
||||
acMatcher, err := NewMphMatcherGroup(domains)
|
||||
@@ -361,50 +366,8 @@ func TestChinaSites(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChinaSitesWithAttrs(t *testing.T) {
|
||||
domains, err := loadGeoSiteDomains("geosite:google@cn")
|
||||
common.Must(err)
|
||||
|
||||
acMatcher, err := NewMphMatcherGroup(domains)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
Domain string
|
||||
Output bool
|
||||
}
|
||||
testCases := []TestCase{
|
||||
{
|
||||
Domain: "google.cn",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Domain: "recaptcha.net",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Domain: "164.com",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Domain: "164.com",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < 1024; i++ {
|
||||
testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
r := acMatcher.ApplyDomain(testCase.Domain)
|
||||
if r != testCase.Output {
|
||||
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
domains, err := loadGeoSiteDomains("geosite:cn")
|
||||
domains, err := loadGeoSite("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := NewMphMatcherGroup(domains)
|
||||
@@ -449,11 +412,11 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
var geoips []*GeoIP
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("geoip:cn")
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CN",
|
||||
Cidr: ips.Cidr,
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -462,25 +425,25 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "JP",
|
||||
Cidr: ips.Cidr,
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("geoip:ca")
|
||||
ips, err := loadGeoIP("CA")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CA",
|
||||
Cidr: ips.Cidr,
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("geoip:us")
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "US",
|
||||
Cidr: ips.Cidr,
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+11
-99
@@ -7,10 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
@@ -76,19 +74,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
}
|
||||
|
||||
if len(rr.Geoip) > 0 {
|
||||
geoip := rr.Geoip
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
var err error
|
||||
geoip, err = GetGeoIPList(rr.Geoip)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build geoip from mmap").Base(err)
|
||||
}
|
||||
}
|
||||
cond, err := NewIPMatcher(geoip, MatcherAsType_Target)
|
||||
cond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.Geoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.SourceGeoip) > 0 {
|
||||
@@ -97,6 +89,8 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.SourceGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.LocalGeoip) > 0 {
|
||||
@@ -106,24 +100,19 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
}
|
||||
conds.Add(cond)
|
||||
errors.LogWarning(context.Background(), "Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \"localIP\" rule condition does not work properly on UDP inbound connections that listen on all interfaces")
|
||||
rr.LocalGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.Domain) > 0 {
|
||||
domains := rr.Domain
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
var err error
|
||||
domains, err = GetDomainList(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domains from mmap").Base(err)
|
||||
}
|
||||
}
|
||||
|
||||
matcher, err := NewMphMatcherGroup(domains)
|
||||
matcher, err := NewMphMatcherGroup(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
|
||||
}
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(domains), " domain rule(s)")
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
|
||||
conds.Add(matcher)
|
||||
rr.Domain = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.Process) > 0 {
|
||||
@@ -183,80 +172,3 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
|
||||
return nil, errors.New("unrecognized balancer type")
|
||||
}
|
||||
}
|
||||
|
||||
func GetGeoIPList(ips []*GeoIP) ([]*GeoIP, error) {
|
||||
geoipList := []*GeoIP{}
|
||||
for _, ip := range ips {
|
||||
if ip.CountryCode != "" {
|
||||
val := strings.Split(ip.CountryCode, "_")
|
||||
fileName := "geoip.dat"
|
||||
if len(val) == 2 {
|
||||
fileName = strings.ToLower(val[0])
|
||||
}
|
||||
bs, err := filesystem.ReadAsset(fileName)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", fileName).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(ip.CountryCode))
|
||||
|
||||
var geoip GeoIP
|
||||
|
||||
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
||||
return nil, errors.New("failed Unmarshal :").Base(err)
|
||||
}
|
||||
geoipList = append(geoipList, &geoip)
|
||||
|
||||
} else {
|
||||
geoipList = append(geoipList, ip)
|
||||
}
|
||||
}
|
||||
return geoipList, nil
|
||||
|
||||
}
|
||||
|
||||
func GetDomainList(domains []*Domain) ([]*Domain, error) {
|
||||
domainList := []*Domain{}
|
||||
for _, domain := range domains {
|
||||
val := strings.Split(domain.Value, "_")
|
||||
|
||||
if len(val) >= 2 {
|
||||
|
||||
fileName := val[0]
|
||||
code := val[1]
|
||||
|
||||
bs, err := filesystem.ReadAsset(fileName)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", fileName).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(code))
|
||||
var geosite GeoSite
|
||||
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return nil, errors.New("failed Unmarshal :").Base(err)
|
||||
}
|
||||
|
||||
// parse attr
|
||||
if len(val) == 3 {
|
||||
siteWithAttr := strings.Split(val[2], ",")
|
||||
attrs := ParseAttrs(siteWithAttr)
|
||||
|
||||
if !attrs.IsEmpty() {
|
||||
filteredDomains := make([]*Domain, 0, len(domains))
|
||||
for _, domain := range geosite.Domain {
|
||||
if attrs.Match(domain) {
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
geosite.Domain = filteredDomains
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
domainList = append(domainList, geosite.Domain...)
|
||||
|
||||
} else {
|
||||
domainList = append(domainList, domain)
|
||||
}
|
||||
}
|
||||
return domainList, nil
|
||||
}
|
||||
|
||||
+16
-1
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
sync "sync"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -181,6 +181,21 @@ func (r *Router) RemoveRule(tag string) error {
|
||||
return errors.New("empty tag name!")
|
||||
|
||||
}
|
||||
|
||||
// ListRule implements routing.Router
|
||||
func (r *Router) ListRule() []routing.Route {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
ruleList := make([]routing.Route, 0)
|
||||
for _, rule := range r.rules {
|
||||
ruleList = append(ruleList, &Route{
|
||||
outboundTag: rule.Tag,
|
||||
ruleTag: rule.RuleTag,
|
||||
})
|
||||
}
|
||||
return ruleList
|
||||
}
|
||||
|
||||
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
||||
// SkipDNSResolve is set from DNS module.
|
||||
// the DOH remote server maybe a domain name,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package antireplay
|
||||
|
||||
type GeneralizedReplayFilter interface {
|
||||
Interval() int64
|
||||
Check(sum []byte) bool
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package antireplay
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMapFilter(b *testing.B) {
|
||||
filter := NewMapFilter[[16]byte](120)
|
||||
var sample [16]byte
|
||||
reader := bufio.NewReader(rand.Reader)
|
||||
reader.Read(sample[:])
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
reader.Read(sample[:])
|
||||
filter.Check(sample)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapFilter(t *testing.T) {
|
||||
filter := NewMapFilter[[16]byte](120)
|
||||
var sample [16]byte
|
||||
rand.Read(sample[:])
|
||||
filter.Check(sample)
|
||||
if filter.Check(sample) {
|
||||
t.Error("Unexpected true negative")
|
||||
}
|
||||
sample[0]++
|
||||
if !filter.Check(sample) {
|
||||
t.Error("Unexpected false positive")
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package antireplay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
ss_bloomring "github.com/v2fly/ss-bloomring"
|
||||
)
|
||||
|
||||
type BloomRing struct {
|
||||
*ss_bloomring.BloomRing
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
func (b BloomRing) Interval() int64 {
|
||||
return 9999999
|
||||
}
|
||||
|
||||
func (b BloomRing) Check(sum []byte) bool {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.Test(sum) {
|
||||
return false
|
||||
}
|
||||
b.Add(sum)
|
||||
return true
|
||||
}
|
||||
|
||||
func NewBloomRing() BloomRing {
|
||||
const (
|
||||
DefaultSFCapacity = 1e6
|
||||
// FalsePositiveRate
|
||||
DefaultSFFPR = 1e-6
|
||||
DefaultSFSlot = 10
|
||||
)
|
||||
return BloomRing{ss_bloomring.NewBloomRing(DefaultSFSlot, DefaultSFCapacity, DefaultSFFPR), &sync.Mutex{}}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package antireplay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReplayFilter checks for replay attacks.
|
||||
type ReplayFilter[T comparable] struct {
|
||||
lock sync.Mutex
|
||||
poolA map[T]struct{}
|
||||
poolB map[T]struct{}
|
||||
interval time.Duration
|
||||
lastClean time.Time
|
||||
}
|
||||
|
||||
// NewMapFilter create a new filter with specifying the expiration time interval in seconds.
|
||||
func NewMapFilter[T comparable](interval int64) *ReplayFilter[T] {
|
||||
filter := &ReplayFilter[T]{
|
||||
poolA: make(map[T]struct{}),
|
||||
poolB: make(map[T]struct{}),
|
||||
interval: time.Duration(interval) * time.Second,
|
||||
lastClean: time.Now(),
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
// Check determines if there are duplicate records.
|
||||
func (filter *ReplayFilter[T]) Check(sum T) bool {
|
||||
filter.lock.Lock()
|
||||
defer filter.lock.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(filter.lastClean) >= filter.interval {
|
||||
filter.poolB = filter.poolA
|
||||
filter.poolA = make(map[T]struct{})
|
||||
filter.lastClean = now
|
||||
}
|
||||
|
||||
_, existsA := filter.poolA[sum]
|
||||
_, existsB := filter.poolB[sum]
|
||||
if !existsA && !existsB {
|
||||
filter.poolA[sum] = struct{}{}
|
||||
}
|
||||
return !(existsA || existsB)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package antireplay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
cuckoo "github.com/seiflotfy/cuckoofilter"
|
||||
)
|
||||
|
||||
const replayFilterCapacity = 100000
|
||||
|
||||
// ReplayFilter checks for replay attacks.
|
||||
type ReplayFilter struct {
|
||||
lock sync.Mutex
|
||||
poolA *cuckoo.Filter
|
||||
poolB *cuckoo.Filter
|
||||
poolSwap bool
|
||||
lastSwap int64
|
||||
interval int64
|
||||
}
|
||||
|
||||
// NewReplayFilter create a new filter with specifying the expiration time interval in seconds.
|
||||
func NewReplayFilter(interval int64) *ReplayFilter {
|
||||
filter := &ReplayFilter{}
|
||||
filter.interval = interval
|
||||
return filter
|
||||
}
|
||||
|
||||
// Interval in second for expiration time for duplicate records.
|
||||
func (filter *ReplayFilter) Interval() int64 {
|
||||
return filter.interval
|
||||
}
|
||||
|
||||
// Check determines if there are duplicate records.
|
||||
func (filter *ReplayFilter) Check(sum []byte) bool {
|
||||
filter.lock.Lock()
|
||||
defer filter.lock.Unlock()
|
||||
|
||||
now := time.Now().Unix()
|
||||
if filter.lastSwap == 0 {
|
||||
filter.lastSwap = now
|
||||
filter.poolA = cuckoo.NewFilter(replayFilterCapacity)
|
||||
filter.poolB = cuckoo.NewFilter(replayFilterCapacity)
|
||||
}
|
||||
|
||||
elapsed := now - filter.lastSwap
|
||||
if elapsed >= filter.Interval() {
|
||||
if filter.poolSwap {
|
||||
filter.poolA.Reset()
|
||||
} else {
|
||||
filter.poolB.Reset()
|
||||
}
|
||||
filter.poolSwap = !filter.poolSwap
|
||||
filter.lastSwap = now
|
||||
}
|
||||
|
||||
return filter.poolA.InsertUnique(sum) && filter.poolB.InsertUnique(sum)
|
||||
}
|
||||
@@ -4,22 +4,19 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// PrintMigrateFeatureInfo prints a notice of the upcoming feature migration.
|
||||
// Place it after the source feature related config file pharser code.
|
||||
// Important note: Only use this when the target migrating feature is under construction.
|
||||
// Important note: Even when the target migrating feature has finished its construction, this notice can still be used yet before announcing deprecation of the old feature.
|
||||
// PrintNonRemovalDeprecatedFeatureWarning prints a warning of the deprecated feature that won't be removed in the near future.
|
||||
// Do not remove this function even there is no reference to it.
|
||||
func PrintMigrateFeatureInfo(sourceFeature string, targetFeature string) {
|
||||
LogInfo(context.Background(), "The feature "+sourceFeature+" will be migrated to "+targetFeature+" in the future.")
|
||||
func PrintNonRemovalDeprecatedFeatureWarning(sourceFeature string, targetFeature string) {
|
||||
LogWarning(context.Background(), "The feature "+sourceFeature+" is deprecated, not recommended for using and might be removed. Please migrate to "+targetFeature+" as soon as possible.")
|
||||
}
|
||||
|
||||
// PrintDeprecatedFeatureWarning prints a warning for deprecated and going to be removed feature.
|
||||
// Do not remove this function even there is no reference to it.
|
||||
func PrintDeprecatedFeatureWarning(feature string, migrateFeature string) {
|
||||
if len(migrateFeature) > 0 {
|
||||
LogWarning(context.Background(), "This feature "+feature+" is deprecated and being migrated to "+migrateFeature+". Please update your config(s) according to release note and documentation before removal.")
|
||||
LogWarning(context.Background(), "This feature "+feature+" is deprecated, will be removed soon and being migrated to "+migrateFeature+". Please update your config(s) according to release note and documentation before removal.")
|
||||
} else {
|
||||
LogWarning(context.Background(), "This feature "+feature+" is deprecated. Please update your config(s) according to release note and documentation before removal.")
|
||||
LogWarning(context.Background(), "This feature "+feature+" is deprecated and will be removed soon. Please update your config(s) according to release note and documentation before removal.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,16 @@ func PortListFromProto(l *PortList) MemoryPortList {
|
||||
return mpl
|
||||
}
|
||||
|
||||
func (l *PortList) Ports() []uint32 {
|
||||
var ports []uint32
|
||||
for _, r := range l.Range {
|
||||
for i := uint32(r.From); i <= uint32(r.To); i++ {
|
||||
ports = append(ports, i)
|
||||
}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func (mpl MemoryPortList) Contains(port Port) bool {
|
||||
for _, pr := range mpl {
|
||||
if pr.Contains(port) {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if n >= len(buf) {
|
||||
return 0, 0
|
||||
}
|
||||
b := uint64(buf[n])
|
||||
n++
|
||||
x |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, n
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func Find(data, code []byte) []byte {
|
||||
codeL := len(code)
|
||||
if codeL == 0 {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
dataL := len(data)
|
||||
if dataL < 2 {
|
||||
return nil
|
||||
}
|
||||
x, y := DecodeVarint(data[1:])
|
||||
if x == 0 && y == 0 {
|
||||
return nil
|
||||
}
|
||||
headL, bodyL := 1+y, int(x)
|
||||
dataL -= headL
|
||||
if dataL < bodyL {
|
||||
return nil
|
||||
}
|
||||
data = data[headL:]
|
||||
if int(data[1]) == codeL {
|
||||
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
||||
if i+1 == codeL {
|
||||
return data[:bodyL]
|
||||
}
|
||||
}
|
||||
}
|
||||
if dataL == bodyL {
|
||||
return nil
|
||||
}
|
||||
data = data[bodyL:]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
//go:build !windows && !wasm
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
@@ -19,29 +16,6 @@ var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {
|
||||
}
|
||||
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := stat.Size()
|
||||
if size == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// use mmap to save RAM
|
||||
bs, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
|
||||
if err == nil {
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// fallback
|
||||
reader, err := NewFileReader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -55,6 +29,10 @@ func ReadAsset(file string) ([]byte, error) {
|
||||
return ReadFile(platform.GetAssetLocation(file))
|
||||
}
|
||||
|
||||
func OpenAsset(file string) (io.ReadCloser, error) {
|
||||
return NewFileReader(platform.GetAssetLocation(file))
|
||||
}
|
||||
|
||||
func ReadCert(file string) ([]byte, error) {
|
||||
if filepath.IsAbs(file) {
|
||||
return ReadFile(file)
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
//go:build windows || wasm
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
)
|
||||
|
||||
type FileReaderFunc func(path string) (io.ReadCloser, error)
|
||||
|
||||
var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
reader, err := NewFileReader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return buf.ReadAllToBytes(reader)
|
||||
}
|
||||
|
||||
func ReadAsset(file string) ([]byte, error) {
|
||||
return ReadFile(platform.GetAssetLocation(file))
|
||||
}
|
||||
|
||||
func ReadCert(file string) ([]byte, error) {
|
||||
if filepath.IsAbs(file) {
|
||||
return ReadFile(file)
|
||||
}
|
||||
return ReadFile(platform.GetCertLocation(file))
|
||||
}
|
||||
|
||||
func CopyFile(dst string, src string) error {
|
||||
bytes, err := ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(bytes)
|
||||
return err
|
||||
}
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
import "path/filepath"
|
||||
|
||||
func LineSeparator() string {
|
||||
return "\r\n"
|
||||
@@ -14,7 +12,6 @@ func LineSeparator() string {
|
||||
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
|
||||
return filepath.Join(assetPath, file)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
"github.com/apernet/quic-go/quicvarint"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
@@ -87,10 +88,10 @@ func Organization(org string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func MustGenerate(parent *Certificate, opts ...Option) *Certificate {
|
||||
func MustGenerate(parent *Certificate, opts ...Option) (*Certificate, [32]byte) {
|
||||
cert, err := Generate(parent, opts...)
|
||||
common.Must(err)
|
||||
return cert
|
||||
return cert, sha256.Sum256(cert.Certificate)
|
||||
}
|
||||
|
||||
func publicKey(priv interface{}) interface{} {
|
||||
|
||||
@@ -2,10 +2,12 @@ package singbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
B "github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
@@ -29,6 +31,8 @@ type PacketConnWrapper struct {
|
||||
cached buf.MultiBuffer
|
||||
}
|
||||
|
||||
// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()
|
||||
// as a temporarily solution
|
||||
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
if w.cached != nil {
|
||||
mb, bb := buf.SplitFirst(w.cached)
|
||||
@@ -47,10 +51,30 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
return ToSocksaddr(destination), nil
|
||||
}
|
||||
}
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
|
||||
// timeout
|
||||
type readResult struct {
|
||||
mb buf.MultiBuffer
|
||||
err error
|
||||
}
|
||||
c := make(chan readResult, 1)
|
||||
go func() {
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
c <- readResult{mb: mb, err: err}
|
||||
}()
|
||||
var mb buf.MultiBuffer
|
||||
select {
|
||||
case <-time.After(60 * time.Second):
|
||||
common.Close(w.Reader)
|
||||
common.Interrupt(w.Reader)
|
||||
return M.Socksaddr{}, buf.ErrReadTimeout
|
||||
case result := <-c:
|
||||
if result.err != nil {
|
||||
return M.Socksaddr{}, result.err
|
||||
}
|
||||
mb = result.mb
|
||||
}
|
||||
|
||||
nb, bb := buf.SplitFirst(mb)
|
||||
if bb == nil {
|
||||
return M.Socksaddr{}, nil
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ func init() {
|
||||
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" {
|
||||
Show = true
|
||||
}
|
||||
BaseKey = make([]byte, 32)
|
||||
rand.Read(BaseKey)
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV
|
||||
@@ -52,7 +53,7 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
||||
return
|
||||
}
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
||||
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks") {
|
||||
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
||||
h := blake3.New(8, BaseKey)
|
||||
h.Write([]byte(inbound.Source.String()))
|
||||
copy(globalID[:], h.Sum(nil))
|
||||
|
||||
+5
-1
@@ -64,7 +64,11 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) {
|
||||
var files []*ConfigSource
|
||||
supported := []string{"json", "yaml", "toml"}
|
||||
for _, file := range args {
|
||||
format := GetFormat(file)
|
||||
format := "json"
|
||||
if file != "stdin:" {
|
||||
format = GetFormat(file)
|
||||
}
|
||||
|
||||
if slices.Contains(supported, format) {
|
||||
files = append(files, &ConfigSource{
|
||||
Name: file,
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Version_x byte = 25
|
||||
Version_y byte = 12
|
||||
Version_z byte = 8
|
||||
Version_x byte = 26
|
||||
Version_y byte = 1
|
||||
Version_z byte = 23
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -16,6 +16,7 @@ type Router interface {
|
||||
PickRoute(ctx Context) (Route, error)
|
||||
AddRule(config *serial.TypedMessage, shouldAppend bool) error
|
||||
RemoveRule(tag string) error
|
||||
ListRule() []Route
|
||||
}
|
||||
|
||||
// Route is the routing result of Router feature.
|
||||
@@ -65,6 +66,11 @@ func (DefaultRouter) RemoveRule(tag string) error {
|
||||
return common.ErrNoClue
|
||||
}
|
||||
|
||||
// ListRule implements Router.
|
||||
func (DefaultRouter) ListRule() []Route {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (DefaultRouter) Start() error {
|
||||
return nil
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
module github.com/xtls/xray-core
|
||||
|
||||
go 1.25
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/cloudflare/circl v1.6.2
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||
github.com/golang/mock v1.7.0-rc.1
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/miekg/dns v1.1.69
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/quic-go/quic-go v0.58.0
|
||||
github.com/refraction-networking/utls v1.8.1
|
||||
github.com/pires/go-proxyproto v0.9.2
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
h12.io/socks v1.0.3
|
||||
lukechampine.com/blake3 v1.4.1
|
||||
)
|
||||
@@ -36,7 +36,6 @@ require (
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
@@ -44,13 +43,11 @@ require (
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
@@ -38,38 +37,28 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
|
||||
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
@@ -95,16 +84,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
@@ -116,21 +107,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -152,12 +143,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk=
|
||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package duration
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
+4
-4
@@ -1,20 +1,20 @@
|
||||
package duration_test
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/types"
|
||||
)
|
||||
|
||||
type testWithDuration struct {
|
||||
Duration duration.Duration
|
||||
Duration types.Duration
|
||||
}
|
||||
|
||||
func TestDurationJSON(t *testing.T) {
|
||||
expected := &testWithDuration{
|
||||
Duration: duration.Duration(time.Hour),
|
||||
Duration: types.Duration(time.Hour),
|
||||
}
|
||||
data, err := json.Marshal(expected)
|
||||
if err != nil {
|
||||
@@ -0,0 +1,47 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Listable allows a field to be unmarshalled from a single object or a list of objects.
|
||||
// If the json input is a single object, it will be stored as a slice with one element.
|
||||
// If the json input is null or empty or a single empty object, it will be nil.
|
||||
type Listable[T any] []T
|
||||
|
||||
func (l *Listable[T]) UnmarshalJSON(data []byte) error {
|
||||
var v T
|
||||
if len(data) != 0 && !slices.Equal(data, []byte("null")) && data[0] != '[' {
|
||||
if err := json.Unmarshal(data, &v); err == nil {
|
||||
// make the list nil if the single value is the zero value
|
||||
var zero T
|
||||
if reflect.DeepEqual(v, zero) {
|
||||
return nil
|
||||
}
|
||||
*l = []T{v}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return json.Unmarshal(data, (*[]T)(l))
|
||||
}
|
||||
|
||||
// ListableSimpleString is like Listable[string], but able to separate by `~`
|
||||
type ListableSimpleString []string
|
||||
|
||||
func (l *ListableSimpleString) UnmarshalJSON(data []byte) error {
|
||||
var v string
|
||||
if len(data) != 0 && !slices.Equal(data, []byte("null")) && data[0] != '[' {
|
||||
if err := json.Unmarshal(data, &v); err == nil {
|
||||
if v == "" {
|
||||
// make the list nil if the single value is empty string
|
||||
return nil
|
||||
}
|
||||
*l = strings.Split(v, "~")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return json.Unmarshal(data, (*[]string)(l))
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/types"
|
||||
)
|
||||
|
||||
type TestGroup[T any] struct {
|
||||
name string
|
||||
input string
|
||||
expected []T
|
||||
}
|
||||
|
||||
// intentionally to be so chaos
|
||||
var rawJson = `{
|
||||
"field":
|
||||
["value1",
|
||||
"value2", "value3"
|
||||
]
|
||||
}`
|
||||
|
||||
func TestListableUnmarshal(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Field types.Listable[string] `json:"field"`
|
||||
}
|
||||
|
||||
tests := []TestGroup[string]{
|
||||
{
|
||||
name: "SingleString",
|
||||
input: `{"field": "hello"}`,
|
||||
expected: []string{"hello"},
|
||||
},
|
||||
{
|
||||
name: "ArrayString",
|
||||
input: `{"field": ["value1", "value2", "value3"]}`,
|
||||
expected: []string{"value1", "value2", "value3"},
|
||||
},
|
||||
{
|
||||
name: "ComplexArray",
|
||||
input: rawJson,
|
||||
expected: []string{"value1", "value2", "value3"},
|
||||
},
|
||||
{
|
||||
name: "SingleStringWithSpace",
|
||||
input: `{"field": "hello" }`,
|
||||
expected: []string{"hello"},
|
||||
},
|
||||
{
|
||||
name: "ArrayWithSpace",
|
||||
input: `{"field": [ "a", "b" ] }`,
|
||||
expected: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
name: "SingleEmptyString",
|
||||
input: `{"field": ""}`,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Null",
|
||||
input: `{"field": null}`,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Missing (default)",
|
||||
input: `{}`,
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var ts TestStruct
|
||||
err := json.Unmarshal([]byte(tt.input), &ts)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal failed: %v", err)
|
||||
}
|
||||
if !slices.Equal([]string(ts.Field), tt.expected) {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, ts.Field)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListableInt(t *testing.T) {
|
||||
tests := []TestGroup[int]{
|
||||
{
|
||||
name: "SingleInt",
|
||||
input: `123`,
|
||||
expected: []int{123},
|
||||
},
|
||||
{
|
||||
name: "ArrayInt",
|
||||
input: `[1, 2]`,
|
||||
expected: []int{1, 2},
|
||||
},
|
||||
{
|
||||
name: "Null",
|
||||
input: `null`,
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var l types.Listable[int]
|
||||
err := json.Unmarshal([]byte(tt.input), &l)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal failed: %v", err)
|
||||
}
|
||||
if !slices.Equal([]int(l), tt.expected) {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, l)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListableSimpleString(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Field types.ListableSimpleString `json:"field"`
|
||||
}
|
||||
|
||||
tests := []TestGroup[string]{
|
||||
{
|
||||
name: "SingleString",
|
||||
input: `{"field": "singleValue"}`,
|
||||
expected: []string{"singleValue"},
|
||||
},
|
||||
{
|
||||
name: "ArrayString",
|
||||
input: `{"field": ["value1", "value2", "value3"]}`,
|
||||
expected: []string{"value1", "value2", "value3"},
|
||||
},
|
||||
{
|
||||
name: "SingleEmptyString",
|
||||
input: `{"field": ""}`,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "WaveSplit",
|
||||
input: `{"field": "value1~value2~value3"}`,
|
||||
expected: []string{"value1", "value2", "value3"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var ts TestStruct
|
||||
err := json.Unmarshal([]byte(tt.input), &ts)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal failed: %v", err)
|
||||
}
|
||||
if !slices.Equal([]string(ts.Field), tt.expected) {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, ts.Field)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+17
-2
@@ -80,6 +80,21 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("failed to parse name server: ", string(data))
|
||||
}
|
||||
|
||||
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
||||
switch t {
|
||||
case router.Domain_Domain:
|
||||
return dns.DomainMatchingType_Subdomain
|
||||
case router.Domain_Full:
|
||||
return dns.DomainMatchingType_Full
|
||||
case router.Domain_Plain:
|
||||
return dns.DomainMatchingType_Keyword
|
||||
case router.Domain_Regex:
|
||||
return dns.DomainMatchingType_Regex
|
||||
default:
|
||||
panic("unknown domain type")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
if c.Address == nil {
|
||||
return nil, errors.New("NameServer address is not specified.")
|
||||
@@ -89,14 +104,14 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
var originalRules []*dns.NameServer_OriginalRule
|
||||
|
||||
for _, rule := range c.Domains {
|
||||
parsedDomain, err := ParseDomainRule(rule)
|
||||
parsedDomain, err := parseDomainRule(rule)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid domain rule: ", rule).Base(err)
|
||||
}
|
||||
|
||||
for _, pd := range parsedDomain {
|
||||
domains = append(domains, &dns.NameServer_PriorityDomain{
|
||||
Type: dns.ToDomainMatchingType(pd.Type),
|
||||
Type: toDomainMatchingType(pd.Type),
|
||||
Domain: pd.Value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/proxy/hysteria"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type HysteriaClientConfig struct {
|
||||
Version int32 `json:"version"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
}
|
||||
|
||||
func (c *HysteriaClientConfig) Build() (proto.Message, error) {
|
||||
if c.Version != 2 {
|
||||
return nil, errors.New("version != 2")
|
||||
}
|
||||
|
||||
config := &hysteria.ClientConfig{}
|
||||
config.Version = c.Version
|
||||
config.Server = &protocol.ServerEndpoint{
|
||||
Address: c.Address.Build(),
|
||||
Port: uint32(c.Port),
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
"github.com/xtls/xray-core/app/observatory/burst"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/types"
|
||||
)
|
||||
|
||||
type ObservatoryConfig struct {
|
||||
SubjectSelector []string `json:"subjectSelector"`
|
||||
ProbeURL string `json:"probeURL"`
|
||||
ProbeInterval duration.Duration `json:"probeInterval"`
|
||||
EnableConcurrency bool `json:"enableConcurrency"`
|
||||
SubjectSelector []string `json:"subjectSelector"`
|
||||
ProbeURL string `json:"probeURL"`
|
||||
ProbeInterval types.Duration `json:"probeInterval"`
|
||||
EnableConcurrency bool `json:"enableConcurrency"`
|
||||
}
|
||||
|
||||
func (o *ObservatoryConfig) Build() (proto.Message, error) {
|
||||
|
||||
+152
-84
@@ -1,7 +1,10 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -102,7 +105,7 @@ func (c *RouterConfig) Build() (*router.Config, error) {
|
||||
}
|
||||
|
||||
for _, rawRule := range rawRuleList {
|
||||
rule, err := ParseRule(rawRule)
|
||||
rule, err := parseRule(rawRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,7 +128,7 @@ type RouterRule struct {
|
||||
BalancerTag string `json:"balancerTag"`
|
||||
}
|
||||
|
||||
func ParseIP(s string) (*router.CIDR, error) {
|
||||
func parseIP(s string) (*router.CIDR, error) {
|
||||
var addr, mask string
|
||||
i := strings.Index(s, "/")
|
||||
if i < 0 {
|
||||
@@ -173,91 +176,161 @@ func ParseIP(s string) (*router.CIDR, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(code string) ([]*router.CIDR, error) {
|
||||
return loadIP("geoip.dat", code)
|
||||
}
|
||||
|
||||
var (
|
||||
FileCache = make(map[string][]byte)
|
||||
IPCache = make(map[string]*router.GeoIP)
|
||||
SiteCache = make(map[string]*router.GeoSite)
|
||||
)
|
||||
|
||||
func loadFile(file string) ([]byte, error) {
|
||||
if FileCache[file] == nil {
|
||||
bs, err := filesystem.ReadAsset(file)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to open file: ", file).Base(err)
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
return nil, errors.New("empty file: ", file)
|
||||
}
|
||||
// Do not cache file, may save RAM when there
|
||||
// are many files, but consume CPU each time.
|
||||
return bs, nil
|
||||
FileCache[file] = bs
|
||||
func loadFile(file, code string) ([]byte, error) {
|
||||
runtime.GC()
|
||||
r, err := filesystem.OpenAsset(file)
|
||||
defer r.Close()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to open file: ", file).Base(err)
|
||||
}
|
||||
return FileCache[file], nil
|
||||
bs := find(r, []byte(code))
|
||||
if bs == nil {
|
||||
return nil, errors.New("code not found in ", file, ": ", code)
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func loadIP(file, code string) ([]*router.CIDR, error) {
|
||||
index := file + ":" + code
|
||||
if IPCache[index] == nil {
|
||||
var geoip router.GeoIP
|
||||
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
// dont pass code becuase we have country code in top level router.GeoIP
|
||||
geoip = router.GeoIP{Cidr: []*router.CIDR{}}
|
||||
} else {
|
||||
bs, err := loadFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", file).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(code))
|
||||
if bs == nil {
|
||||
return nil, errors.New("code not found in ", file, ": ", code)
|
||||
}
|
||||
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
||||
return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err)
|
||||
}
|
||||
}
|
||||
defer runtime.GC() // or debug.FreeOSMemory()
|
||||
return geoip.Cidr, nil // do not cache geoip
|
||||
IPCache[index] = &geoip
|
||||
bs, err := loadFile(file, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return IPCache[index].Cidr, nil
|
||||
var geoip router.GeoIP
|
||||
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
||||
return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err)
|
||||
}
|
||||
defer runtime.GC() // or debug.FreeOSMemory()
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
|
||||
func loadSite(file, code string) ([]*router.Domain, error) {
|
||||
index := file + ":" + code
|
||||
if SiteCache[index] == nil {
|
||||
var geosite router.GeoSite
|
||||
bs, err := loadFile(file, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var geosite router.GeoSite
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err)
|
||||
}
|
||||
defer runtime.GC() // or debug.FreeOSMemory()
|
||||
return geosite.Domain, nil
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
// pass file:code so can build optimized matcher later
|
||||
domain := router.Domain{Value: file + "_" + code}
|
||||
geosite = router.GeoSite{Domain: []*router.Domain{&domain}}
|
||||
func decodeVarint(r *bufio.Reader) (uint64, error) {
|
||||
var x uint64
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, nil
|
||||
}
|
||||
}
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, errors.New("varint overflow")
|
||||
}
|
||||
|
||||
} else {
|
||||
func find(r io.Reader, code []byte) []byte {
|
||||
codeL := len(code)
|
||||
if codeL == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bs, err := loadFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", file).Base(err)
|
||||
}
|
||||
bs = filesystem.Find(bs, []byte(code))
|
||||
if bs == nil {
|
||||
return nil, errors.New("list not found in ", file, ": ", code)
|
||||
}
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err)
|
||||
br := bufio.NewReaderSize(r, 64*1024)
|
||||
need := 2 + codeL
|
||||
prefixBuf := make([]byte, need)
|
||||
|
||||
for {
|
||||
if _, err := br.ReadByte(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
x, err := decodeVarint(br)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
bodyL := int(x)
|
||||
if bodyL <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
prefixL := bodyL
|
||||
if prefixL > need {
|
||||
prefixL = need
|
||||
}
|
||||
prefix := prefixBuf[:prefixL]
|
||||
if _, err := io.ReadFull(br, prefix); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
match := false
|
||||
if bodyL >= need {
|
||||
if int(prefix[1]) == codeL && bytes.Equal(prefix[2:need], code) {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
defer runtime.GC() // or debug.FreeOSMemory()
|
||||
return geosite.Domain, nil // do not cache geosite
|
||||
SiteCache[index] = &geosite
|
||||
remain := bodyL - prefixL
|
||||
if match {
|
||||
out := make([]byte, bodyL)
|
||||
copy(out, prefix)
|
||||
if remain > 0 {
|
||||
if _, err := io.ReadFull(br, out[prefixL:]); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
if remain > 0 {
|
||||
if _, err := br.Discard(remain); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return SiteCache[index].Domain, nil
|
||||
}
|
||||
|
||||
type AttributeMatcher interface {
|
||||
Match(*router.Domain) bool
|
||||
}
|
||||
|
||||
type BooleanMatcher string
|
||||
|
||||
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if attr.Key == string(m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
for _, matcher := range al.matcher {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
lc := strings.ToLower(attr)
|
||||
al.matcher = append(al.matcher, BooleanMatcher(lc))
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
||||
@@ -266,7 +339,7 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
|
||||
return nil, errors.New("empty site")
|
||||
}
|
||||
country := strings.ToUpper(parts[0])
|
||||
attrs := router.ParseAttrs(parts[1:])
|
||||
attrs := parseAttrs(parts[1:])
|
||||
domains, err := loadSite(file, country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -276,11 +349,6 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
||||
domains[0].Value = domains[0].Value + "_" + strings.Join(parts[1:], ",")
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
@@ -291,7 +359,7 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
|
||||
return filteredDomains, nil
|
||||
}
|
||||
|
||||
func ParseDomainRule(domain string) ([]*router.Domain, error) {
|
||||
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
||||
if strings.HasPrefix(domain, "geosite:") {
|
||||
country := strings.ToUpper(domain[8:])
|
||||
domains, err := loadGeositeWithAttr("geosite.dat", country)
|
||||
@@ -376,7 +444,7 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
|
||||
if len(country) == 0 {
|
||||
return nil, errors.New("empty country name in rule")
|
||||
}
|
||||
geoip, err := loadGeoIP(strings.ToUpper(country))
|
||||
geoip, err := loadIP("geoip.dat", strings.ToUpper(country))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load GeoIP: ", country).Base(err)
|
||||
}
|
||||
@@ -430,7 +498,7 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipRule, err := ParseIP(ip)
|
||||
ipRule, err := parseIP(ip)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid IP: ", ip).Base(err)
|
||||
}
|
||||
@@ -489,7 +557,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
|
||||
if rawFieldRule.Domain != nil {
|
||||
for _, domain := range *rawFieldRule.Domain {
|
||||
rules, err := ParseDomainRule(domain)
|
||||
rules, err := parseDomainRule(domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
@@ -499,7 +567,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
|
||||
if rawFieldRule.Domains != nil {
|
||||
for _, domain := range *rawFieldRule.Domains {
|
||||
rules, err := ParseDomainRule(domain)
|
||||
rules, err := parseDomainRule(domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
@@ -584,7 +652,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
func parseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(msg, rawRule)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory/burst"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,23 +37,23 @@ type strategyLeastLoadConfig struct {
|
||||
// weight settings
|
||||
Costs []*router.StrategyWeight `json:"costs,omitempty"`
|
||||
// ping rtt baselines
|
||||
Baselines []duration.Duration `json:"baselines,omitempty"`
|
||||
Baselines []types.Duration `json:"baselines,omitempty"`
|
||||
// expected nodes count to select
|
||||
Expected int32 `json:"expected,omitempty"`
|
||||
// max acceptable rtt, filter away high delay nodes. default 0
|
||||
MaxRTT duration.Duration `json:"maxRTT,omitempty"`
|
||||
MaxRTT types.Duration `json:"maxRTT,omitempty"`
|
||||
// acceptable failure rate
|
||||
Tolerance float64 `json:"tolerance,omitempty"`
|
||||
}
|
||||
|
||||
// healthCheckSettings holds settings for health Checker
|
||||
type healthCheckSettings struct {
|
||||
Destination string `json:"destination"`
|
||||
Connectivity string `json:"connectivity"`
|
||||
Interval duration.Duration `json:"interval"`
|
||||
SamplingCount int `json:"sampling"`
|
||||
Timeout duration.Duration `json:"timeout"`
|
||||
HttpMethod string `json:"httpMethod"`
|
||||
Destination string `json:"destination"`
|
||||
Connectivity string `json:"connectivity"`
|
||||
Interval types.Duration `json:"interval"`
|
||||
SamplingCount int `json:"sampling"`
|
||||
Timeout types.Duration `json:"timeout"`
|
||||
HttpMethod string `json:"httpMethod"`
|
||||
}
|
||||
|
||||
func (h healthCheckSettings) Build() (proto.Message, error) {
|
||||
|
||||
@@ -46,10 +46,11 @@ type ShadowsocksServerConfig struct {
|
||||
Email string `json:"email"`
|
||||
Users []*ShadowsocksUserConfig `json:"clients"`
|
||||
NetworkList *NetworkList `json:"network"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||
|
||||
if C.Contains(shadowaead_2022.List, v.Cipher) {
|
||||
return buildShadowsocks2022(v)
|
||||
}
|
||||
@@ -62,7 +63,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
account := &shadowsocks.Account{
|
||||
Password: user.Password,
|
||||
CipherType: cipherFromString(user.Cipher),
|
||||
IvCheck: v.IVCheck,
|
||||
}
|
||||
if account.Password == "" {
|
||||
return nil, errors.New("Shadowsocks password is not specified.")
|
||||
@@ -81,7 +81,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
account := &shadowsocks.Account{
|
||||
Password: v.Password,
|
||||
CipherType: cipherFromString(v.Cipher),
|
||||
IvCheck: v.IVCheck,
|
||||
}
|
||||
if account.Password == "" {
|
||||
return nil, errors.New("Shadowsocks password is not specified.")
|
||||
@@ -166,7 +165,6 @@ type ShadowsocksServerTarget struct {
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
}
|
||||
@@ -178,13 +176,14 @@ type ShadowsocksClientConfig struct {
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
IVCheck bool `json:"ivCheck"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||
|
||||
if v.Address != nil {
|
||||
v.Servers = []*ShadowsocksServerTarget{
|
||||
{
|
||||
@@ -194,7 +193,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
Email: v.Email,
|
||||
Cipher: v.Cipher,
|
||||
Password: v.Password,
|
||||
IVCheck: v.IVCheck,
|
||||
UoT: v.UoT,
|
||||
UoTVersion: v.UoTVersion,
|
||||
},
|
||||
@@ -250,12 +248,10 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("unknown cipher method: ", server.Cipher)
|
||||
}
|
||||
|
||||
account.IvCheck = server.IVCheck
|
||||
|
||||
ss := &protocol.ServerEndpoint{
|
||||
Address: server.Address.Build(),
|
||||
Port: uint32(server.Port),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Level: uint32(server.Level),
|
||||
Email: server.Email,
|
||||
Account: serial.ToTypedMessage(account),
|
||||
|
||||
@@ -16,7 +16,9 @@ import (
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||
"github.com/xtls/xray-core/transport/internet/httpupgrade"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||
"github.com/xtls/xray-core/transport/internet/kcp"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||
@@ -332,6 +334,177 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
Kilobyte = 1024 * Byte
|
||||
Megabyte = 1024 * Kilobyte
|
||||
Gigabyte = 1024 * Megabyte
|
||||
Terabyte = 1024 * Gigabyte
|
||||
)
|
||||
|
||||
type Bandwidth string
|
||||
|
||||
func (b Bandwidth) Bps() (uint64, error) {
|
||||
s := strings.TrimSpace(strings.ToLower(string(b)))
|
||||
if s == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
idx := len(s)
|
||||
for i, c := range s {
|
||||
if (c < '0' || c > '9') && c != '.' {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
numStr := s[:idx]
|
||||
unit := strings.TrimSpace(s[idx:])
|
||||
|
||||
val, err := strconv.ParseFloat(numStr, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
mul := uint64(1)
|
||||
switch unit {
|
||||
case "", "b", "bps":
|
||||
mul = Byte
|
||||
case "k", "kb", "kbps":
|
||||
mul = Kilobyte
|
||||
case "m", "mb", "mbps":
|
||||
mul = Megabyte
|
||||
case "g", "gb", "gbps":
|
||||
mul = Gigabyte
|
||||
case "t", "tb", "tbps":
|
||||
mul = Terabyte
|
||||
default:
|
||||
return 0, errors.New("unsupported unit: " + unit)
|
||||
}
|
||||
|
||||
return uint64(val*float64(mul)) / 8, nil
|
||||
}
|
||||
|
||||
type UdpHop struct {
|
||||
PortList json.RawMessage `json:"port"`
|
||||
Interval *Int32Range `json:"interval"`
|
||||
}
|
||||
|
||||
type HysteriaConfig struct {
|
||||
Version int32 `json:"version"`
|
||||
Auth string `json:"auth"`
|
||||
Congestion string `json:"congestion"`
|
||||
Up Bandwidth `json:"up"`
|
||||
Down Bandwidth `json:"down"`
|
||||
UdpHop UdpHop `json:"udphop"`
|
||||
|
||||
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
|
||||
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
|
||||
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
|
||||
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
|
||||
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
||||
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
||||
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
||||
}
|
||||
|
||||
func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
if c.Version != 2 {
|
||||
return nil, errors.New("version != 2")
|
||||
}
|
||||
|
||||
up, err := c.Up.Bps()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
down, err := c.Down.Bps()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Congestion = strings.ToLower(c.Congestion)
|
||||
if c.Congestion == "force-brutal" && up == 0 {
|
||||
return nil, errors.New("force-brutal require up")
|
||||
}
|
||||
|
||||
var hop *PortList
|
||||
if err := json.Unmarshal(c.UdpHop.PortList, &hop); err != nil {
|
||||
hop = &PortList{}
|
||||
}
|
||||
|
||||
var inertvalMin, inertvalMax int64
|
||||
if c.UdpHop.Interval != nil {
|
||||
inertvalMin = int64(c.UdpHop.Interval.From)
|
||||
inertvalMax = int64(c.UdpHop.Interval.To)
|
||||
}
|
||||
|
||||
if up > 0 && up < 65536 {
|
||||
return nil, errors.New("Up must be at least 65536 Bps")
|
||||
}
|
||||
if down > 0 && down < 65536 {
|
||||
return nil, errors.New("Down must be at least 65536 Bps")
|
||||
}
|
||||
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
|
||||
return nil, errors.New("Interval must be at least 5")
|
||||
}
|
||||
|
||||
if c.InitStreamReceiveWindow > 0 && c.InitStreamReceiveWindow < 16384 {
|
||||
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
||||
}
|
||||
if c.MaxStreamReceiveWindow > 0 && c.MaxStreamReceiveWindow < 16384 {
|
||||
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
|
||||
}
|
||||
if c.InitConnectionReceiveWindow > 0 && c.InitConnectionReceiveWindow < 16384 {
|
||||
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
|
||||
}
|
||||
if c.MaxConnectionReceiveWindow > 0 && c.MaxConnectionReceiveWindow < 16384 {
|
||||
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
|
||||
}
|
||||
if c.MaxIdleTimeout != 0 && (c.MaxIdleTimeout < 4 || c.MaxIdleTimeout > 120) {
|
||||
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
|
||||
}
|
||||
if c.KeepAlivePeriod != 0 && (c.KeepAlivePeriod < 2 || c.KeepAlivePeriod > 60) {
|
||||
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
|
||||
}
|
||||
|
||||
config := &hysteria.Config{}
|
||||
config.Version = c.Version
|
||||
config.Auth = c.Auth
|
||||
config.Congestion = c.Congestion
|
||||
config.Up = up
|
||||
config.Down = down
|
||||
config.Ports = hop.Build().Ports()
|
||||
config.IntervalMin = inertvalMin
|
||||
config.IntervalMax = inertvalMax
|
||||
config.InitStreamReceiveWindow = c.InitStreamReceiveWindow
|
||||
config.MaxStreamReceiveWindow = c.MaxStreamReceiveWindow
|
||||
config.InitConnReceiveWindow = c.InitConnectionReceiveWindow
|
||||
config.MaxConnReceiveWindow = c.MaxConnectionReceiveWindow
|
||||
config.MaxIdleTimeout = c.MaxIdleTimeout
|
||||
config.KeepAlivePeriod = c.KeepAlivePeriod
|
||||
config.DisablePathMtuDiscovery = c.DisablePathMTUDiscovery
|
||||
|
||||
if config.InitStreamReceiveWindow == 0 {
|
||||
config.InitStreamReceiveWindow = 8388608
|
||||
}
|
||||
if config.MaxStreamReceiveWindow == 0 {
|
||||
config.MaxStreamReceiveWindow = 8388608
|
||||
}
|
||||
if config.InitConnReceiveWindow == 0 {
|
||||
config.InitConnReceiveWindow = 8388608 * 5 / 2
|
||||
}
|
||||
if config.MaxConnReceiveWindow == 0 {
|
||||
config.MaxConnReceiveWindow = 8388608 * 5 / 2
|
||||
}
|
||||
if config.MaxIdleTimeout == 0 {
|
||||
config.MaxIdleTimeout = 30
|
||||
}
|
||||
// if config.KeepAlivePeriod == 0 {
|
||||
// config.KeepAlivePeriod = 10
|
||||
// }
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||
if len(f) > 0 {
|
||||
return filesystem.ReadCert(f)
|
||||
@@ -395,7 +568,7 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Insecure bool `json:"allowInsecure"`
|
||||
AllowInsecure bool `json:"allowInsecure"`
|
||||
Certs []*TLSCertConfig `json:"certificates"`
|
||||
ServerName string `json:"serverName"`
|
||||
ALPN *StringList `json:"alpn"`
|
||||
@@ -406,10 +579,10 @@ type TLSConfig struct {
|
||||
CipherSuites string `json:"cipherSuites"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
RejectUnknownSNI bool `json:"rejectUnknownSni"`
|
||||
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
|
||||
CurvePreferences *StringList `json:"curvePreferences"`
|
||||
MasterKeyLog string `json:"masterKeyLog"`
|
||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
|
||||
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
|
||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||
ECHServerKeys string `json:"echServerKeys"`
|
||||
ECHConfigList string `json:"echConfigList"`
|
||||
@@ -429,7 +602,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.Certificate[idx] = cert
|
||||
}
|
||||
serverName := c.ServerName
|
||||
config.AllowInsecure = c.Insecure
|
||||
if len(c.ServerName) > 0 {
|
||||
config.ServerName = serverName
|
||||
}
|
||||
@@ -456,12 +628,13 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
|
||||
}
|
||||
config.RejectUnknownSni = c.RejectUnknownSNI
|
||||
config.MasterKeyLog = c.MasterKeyLog
|
||||
|
||||
if c.AllowInsecure {
|
||||
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
||||
}
|
||||
if c.PinnedPeerCertSha256 != "" {
|
||||
config.PinnedPeerCertSha256 = [][]byte{}
|
||||
// Split by tilde separator
|
||||
hashes := strings.Split(c.PinnedPeerCertSha256, "~")
|
||||
for _, v := range hashes {
|
||||
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
continue
|
||||
@@ -474,12 +647,18 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
}
|
||||
|
||||
config.MasterKeyLog = c.MasterKeyLog
|
||||
|
||||
if c.ServerNameToVerify != "" {
|
||||
return nil, errors.PrintRemovedFeatureError(`"serverNameToVerify"`, `"verifyPeerCertInNames"`)
|
||||
if c.VerifyPeerCertInNames != nil {
|
||||
return nil, errors.PrintRemovedFeatureError(`"verifyPeerCertInNames"`, `"verifyPeerCertByName"`)
|
||||
}
|
||||
if c.VerifyPeerCertByName != "" {
|
||||
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
config.VerifyPeerCertByName = append(config.VerifyPeerCertByName, v)
|
||||
}
|
||||
}
|
||||
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
||||
|
||||
if c.ECHServerKeys != "" {
|
||||
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
|
||||
@@ -734,18 +913,20 @@ func (p TransportProtocol) Build() (string, error) {
|
||||
case "kcp", "mkcp":
|
||||
return "mkcp", nil
|
||||
case "grpc":
|
||||
errors.PrintDeprecatedFeatureWarning("gRPC transport (with unnecessary costs, etc.)", "XHTTP stream-up H2")
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("gRPC transport (with unnecessary costs, etc.)", "XHTTP stream-up H2")
|
||||
return "grpc", nil
|
||||
case "ws", "websocket":
|
||||
errors.PrintDeprecatedFeatureWarning("WebSocket transport (with ALPN http/1.1, etc.)", "XHTTP H2 & H3")
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("WebSocket transport (with ALPN http/1.1, etc.)", "XHTTP H2 & H3")
|
||||
return "websocket", nil
|
||||
case "httpupgrade":
|
||||
errors.PrintDeprecatedFeatureWarning("HTTPUpgrade transport (with ALPN http/1.1, etc.)", "XHTTP H2 & H3")
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("HTTPUpgrade transport (with ALPN http/1.1, etc.)", "XHTTP H2 & H3")
|
||||
return "httpupgrade", nil
|
||||
case "h2", "h3", "http":
|
||||
return "", errors.PrintRemovedFeatureError("HTTP transport (without header padding, etc.)", "XHTTP stream-one H2 & H3")
|
||||
case "quic":
|
||||
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
||||
case "hysteria":
|
||||
return "hysteria", nil
|
||||
default:
|
||||
return "", errors.New("Config: unknown transport protocol: ", p)
|
||||
}
|
||||
@@ -928,11 +1109,54 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"salamander": func() interface{} { return new(Salamander) },
|
||||
}, "type", "settings")
|
||||
)
|
||||
|
||||
type Salamander struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (c *Salamander) Build() (proto.Message, error) {
|
||||
config := &salamander.Config{}
|
||||
config.Password = c.Password
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type FinalMask struct {
|
||||
Type string `json:"type"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
}
|
||||
|
||||
func (c *FinalMask) Build(tcpmaskLoader bool) (proto.Message, error) {
|
||||
loader := udpmaskLoader
|
||||
if tcpmaskLoader {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
|
||||
settings := []byte("{}")
|
||||
if c.Settings != nil {
|
||||
settings = ([]byte)(*c.Settings)
|
||||
}
|
||||
rawConfig, err := loader.LoadWithID(settings, c.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts, err := rawConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Network *TransportProtocol `json:"network"`
|
||||
Security string `json:"security"`
|
||||
Udpmasks []*FinalMask `json:"udpmasks"`
|
||||
TLSSettings *TLSConfig `json:"tlsSettings"`
|
||||
REALITYSettings *REALITYConfig `json:"realitySettings"`
|
||||
RAWSettings *TCPConfig `json:"rawSettings"`
|
||||
@@ -943,6 +1167,7 @@ type StreamConfig struct {
|
||||
GRPCSettings *GRPCConfig `json:"grpcSettings"`
|
||||
WSSettings *WebSocketConfig `json:"wsSettings"`
|
||||
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
||||
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
||||
SocketSettings *SocketConfig `json:"sockopt"`
|
||||
}
|
||||
|
||||
@@ -962,6 +1187,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
}
|
||||
config.ProtocolName = protocol
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.Security) {
|
||||
case "", "none":
|
||||
case "tls":
|
||||
@@ -995,6 +1221,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
default:
|
||||
return nil, errors.New(`Unknown security "` + c.Security + `".`)
|
||||
}
|
||||
|
||||
if c.RAWSettings != nil {
|
||||
c.TCPSettings = c.RAWSettings
|
||||
}
|
||||
@@ -1061,6 +1288,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
Settings: serial.ToTypedMessage(hs),
|
||||
})
|
||||
}
|
||||
if c.HysteriaSettings != nil {
|
||||
hs, err := c.HysteriaSettings.Build()
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to build Hysteria config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "hysteria",
|
||||
Settings: serial.ToTypedMessage(hs),
|
||||
})
|
||||
}
|
||||
if c.SocketSettings != nil {
|
||||
ss, err := c.SocketSettings.Build()
|
||||
if err != nil {
|
||||
@@ -1068,6 +1305,15 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
}
|
||||
config.SocketSettings = ss
|
||||
}
|
||||
|
||||
for _, mask := range c.Udpmasks {
|
||||
u, err := mask.Build(false)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build mask with type ", mask.Type).Base(err)
|
||||
}
|
||||
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ type TrojanClientConfig struct {
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *TrojanClientConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||
|
||||
if c.Address != nil {
|
||||
c.Servers = []*TrojanServerTarget{
|
||||
{
|
||||
@@ -74,7 +76,7 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) {
|
||||
config.Server = &protocol.ServerEndpoint{
|
||||
Address: rec.Address.Build(),
|
||||
Port: uint32(rec.Port),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Level: uint32(rec.Level),
|
||||
Email: rec.Email,
|
||||
Account: serial.ToTypedMessage(&trojan.Account{
|
||||
@@ -115,6 +117,8 @@ type TrojanServerConfig struct {
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||
|
||||
config := &trojan.ServerConfig{
|
||||
Users: make([]*protocol.User, len(c.Clients)),
|
||||
}
|
||||
|
||||
+6
-5
@@ -42,8 +42,6 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
config := new(inbound.Config)
|
||||
config.Clients = make([]*protocol.User, len(c.Clients))
|
||||
switch c.Flow {
|
||||
case vless.None:
|
||||
c.Flow = ""
|
||||
case "", vless.XRV:
|
||||
default:
|
||||
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
|
||||
@@ -67,12 +65,13 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
switch account.Flow {
|
||||
case "":
|
||||
account.Flow = c.Flow
|
||||
case vless.None:
|
||||
account.Flow = ""
|
||||
case vless.XRV:
|
||||
default:
|
||||
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||
}
|
||||
if account.Flow == "" {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("VLESS (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||
}
|
||||
|
||||
if len(account.Testseed) < 4 {
|
||||
account.Testseed = c.Testseed
|
||||
@@ -280,7 +279,9 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||
account.Id = u.String()
|
||||
|
||||
switch account.Flow {
|
||||
case "", vless.XRV, vless.XRV + "-udp443":
|
||||
case "":
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("VLESS (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||
case vless.XRV, vless.XRV + "-udp443":
|
||||
default:
|
||||
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||
}
|
||||
|
||||
+6
-2
@@ -58,12 +58,14 @@ func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {
|
||||
}
|
||||
|
||||
type VMessInboundConfig struct {
|
||||
Users []json.RawMessage `json:"clients"`
|
||||
Defaults *VMessDefaultConfig `json:"default"`
|
||||
Users []json.RawMessage `json:"clients"`
|
||||
Defaults *VMessDefaultConfig `json:"default"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("VMess (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||
|
||||
config := &inbound.Config{}
|
||||
|
||||
if c.Defaults != nil {
|
||||
@@ -113,6 +115,8 @@ type VMessOutboundConfig struct {
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessOutboundConfig) Build() (proto.Message, error) {
|
||||
errors.PrintNonRemovalDeprecatedFeatureWarning("VMess (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||
|
||||
config := new(outbound.Config)
|
||||
if c.Address != nil {
|
||||
c.Receivers = []*VMessOutboundTarget{
|
||||
|
||||
+12
-8
@@ -43,6 +43,7 @@ var (
|
||||
"vless": func() interface{} { return new(VLessOutboundConfig) },
|
||||
"vmess": func() interface{} { return new(VMessOutboundConfig) },
|
||||
"trojan": func() interface{} { return new(TrojanClientConfig) },
|
||||
"hysteria": func() interface{} { return new(HysteriaClientConfig) },
|
||||
"dns": func() interface{} { return new(DNSOutboundConfig) },
|
||||
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
|
||||
}, "protocol", "settings")
|
||||
@@ -117,20 +118,23 @@ func (m *MuxConfig) Build() (*proxyman.MultiplexingConfig, error) {
|
||||
}
|
||||
|
||||
type InboundDetourConfig struct {
|
||||
Protocol string `json:"protocol"`
|
||||
PortList *PortList `json:"port"`
|
||||
ListenOn *Address `json:"listen"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||
Protocol string `json:"protocol"`
|
||||
PortList *PortList `json:"port"`
|
||||
ListenOn *Address `json:"listen"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||
receiverSettings := &proxyman.ReceiverConfig{}
|
||||
|
||||
if c.ListenOn == nil {
|
||||
// TUN inbound doesn't need port configuration as it uses network interface instead
|
||||
if strings.ToLower(c.Protocol) == "tun" {
|
||||
// Skip port validation for TUN
|
||||
} else if c.ListenOn == nil {
|
||||
// Listen on anyip, must set PortList
|
||||
if c.PortList == nil {
|
||||
return nil, errors.New("Listen on AnyIP but no Port(s) set in InboundDetour.")
|
||||
|
||||
@@ -29,6 +29,7 @@ var CmdAPI = &base.Command{
|
||||
cmdInboundUserCount,
|
||||
cmdAddRules,
|
||||
cmdRemoveRules,
|
||||
cmdListRules,
|
||||
cmdSourceIpBlock,
|
||||
cmdOnlineStats,
|
||||
cmdOnlineStatsIpList,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
routerService "github.com/xtls/xray-core/app/router/command"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdListRules = &base.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "{{.Exec}} api lsrules [--server=127.0.0.1:8080]",
|
||||
Short: "List routing rules",
|
||||
Long: `
|
||||
List routing rules in Xray.
|
||||
|
||||
Arguments:
|
||||
|
||||
-s, -server <server:port>
|
||||
The API server address. Default 127.0.0.1:8080
|
||||
|
||||
-t, -timeout <seconds>
|
||||
Timeout in seconds for calling API. Default 3
|
||||
|
||||
Example:
|
||||
|
||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080
|
||||
`,
|
||||
Run: executeListRules,
|
||||
}
|
||||
|
||||
func executeListRules(cmd *base.Command, args []string) {
|
||||
setSharedFlags(cmd)
|
||||
cmd.Flag.Parse(args)
|
||||
|
||||
conn, ctx, close := dialAPIServer()
|
||||
defer close()
|
||||
|
||||
client := routerService.NewRoutingServiceClient(conn)
|
||||
resp, err := client.ListRule(ctx, &routerService.ListRuleRequest{})
|
||||
if err != nil {
|
||||
base.Fatalf("failed to list rules: %s", err)
|
||||
}
|
||||
showJSONResponse(resp)
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"context"
|
||||
go_errors "errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
server *protocol.ServerSpec
|
||||
policyManager policy.Manager
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
|
||||
if config.Server == nil {
|
||||
return nil, errors.New(`no target server found`)
|
||||
}
|
||||
server, err := protocol.NewServerSpecFromPB(config.Server)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get server spec").Base(err)
|
||||
}
|
||||
|
||||
v := core.MustFromContext(ctx)
|
||||
client := &Client{
|
||||
server: server,
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
if !ob.Target.IsValid() {
|
||||
return errors.New("target not specified")
|
||||
}
|
||||
ob.Name = "hysteria"
|
||||
ob.CanSpliceCopy = 3
|
||||
target := ob.Target
|
||||
|
||||
conn, err := dialer.Dial(hyCtx.ContextWithRequireDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)
|
||||
if err != nil {
|
||||
return errors.New("failed to find an available destination").AtWarning().Base(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr())
|
||||
|
||||
var newCtx context.Context
|
||||
var newCancel context.CancelFunc
|
||||
if session.TimeoutOnlyFromContext(ctx) {
|
||||
newCtx, newCancel = context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
sessionPolicy := c.policyManager.ForLevel(0)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
timer := signal.CancelAfterInactivity(ctx, func() {
|
||||
cancel()
|
||||
if newCancel != nil {
|
||||
newCancel()
|
||||
}
|
||||
}, sessionPolicy.Timeouts.ConnectionIdle)
|
||||
|
||||
if newCtx != nil {
|
||||
ctx = newCtx
|
||||
}
|
||||
|
||||
if target.Network == net.Network_TCP {
|
||||
requestDone := func() error {
|
||||
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
|
||||
bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
|
||||
err := WriteTCPRequest(bufferedWriter, target.NetAddr())
|
||||
if err != nil {
|
||||
return errors.New("failed to write request").Base(err)
|
||||
}
|
||||
if err := bufferedWriter.SetBuffered(false); err != nil {
|
||||
return err
|
||||
}
|
||||
return buf.Copy(link.Reader, bufferedWriter, buf.UpdateActivity(timer))
|
||||
}
|
||||
|
||||
responseDone := func() error {
|
||||
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
|
||||
ok, msg, err := ReadTCPResponse(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errors.New(msg)
|
||||
}
|
||||
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
|
||||
}
|
||||
|
||||
responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
|
||||
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
|
||||
return errors.New("connection ends").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if target.Network == net.Network_UDP {
|
||||
iConn := stat.TryUnwrapStatsConn(conn)
|
||||
_, ok := iConn.(*hysteria.InterUdpConn)
|
||||
if !ok {
|
||||
return errors.New("udp requires hysteria udp transport")
|
||||
}
|
||||
|
||||
requestDone := func() error {
|
||||
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
|
||||
|
||||
writer := &UDPWriter{
|
||||
Writer: conn,
|
||||
buf: make([]byte, MaxUDPSize),
|
||||
addr: target.NetAddr(),
|
||||
}
|
||||
|
||||
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to transport all UDP request").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
responseDone := func() error {
|
||||
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
|
||||
|
||||
reader := &UDPReader{
|
||||
Reader: conn,
|
||||
df: &Defragger{},
|
||||
}
|
||||
|
||||
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to transport all UDP response").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
responseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))
|
||||
if err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {
|
||||
return errors.New("connection ends").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewClient(ctx, config.(*ClientConfig))
|
||||
}))
|
||||
}
|
||||
|
||||
type UDPWriter struct {
|
||||
Writer io.Writer
|
||||
buf []byte
|
||||
addr string
|
||||
}
|
||||
|
||||
func (w *UDPWriter) sendMsg(msg *UDPMessage) error {
|
||||
msgN := msg.Serialize(w.buf)
|
||||
if msgN < 0 {
|
||||
// Message larger than buffer, silent drop
|
||||
return nil
|
||||
}
|
||||
_, err := w.Writer.Write(w.buf[:msgN])
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for {
|
||||
mb2, b := buf.SplitFirst(mb)
|
||||
mb = mb2
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
addr := w.addr
|
||||
if b.UDP != nil {
|
||||
addr = b.UDP.NetAddr()
|
||||
}
|
||||
msg := &UDPMessage{
|
||||
SessionID: 0,
|
||||
PacketID: 0,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: addr,
|
||||
Data: b.Bytes(),
|
||||
}
|
||||
if err := w.sendMsg(msg); err != nil {
|
||||
var errTooLarge *quic.DatagramTooLargeError
|
||||
if go_errors.As(err, &errTooLarge) {
|
||||
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
||||
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
|
||||
for _, fMsg := range fMsgs {
|
||||
err := w.sendMsg(&fMsg)
|
||||
if err != nil {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.Release()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UDPReader struct {
|
||||
Reader io.Reader
|
||||
df *Defragger
|
||||
}
|
||||
|
||||
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
for {
|
||||
b := buf.New()
|
||||
_, err := b.ReadFrom(r.Reader)
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := ParseUDPMessage(b.Bytes())
|
||||
if err != nil {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
dfMsg := r.df.Feed(msg)
|
||||
if dfMsg == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dest, _ := net.ParseDestination("udp:" + dfMsg.Addr)
|
||||
|
||||
buffer := buf.New()
|
||||
buffer.Write(dfMsg.Data)
|
||||
buffer.UDP = &dest
|
||||
|
||||
return buf.MultiBuffer{buffer}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
|
||||
)
|
||||
|
||||
var (
|
||||
tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
|
||||
// tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.1
|
||||
// source: proxy/hysteria/config.proto
|
||||
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
protocol "github.com/xtls/xray-core/common/protocol"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Server *protocol.ServerEndpoint `protobuf:"bytes,2,opt,name=server,proto3" json:"server,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClientConfig) Reset() {
|
||||
*x = ClientConfig{}
|
||||
mi := &file_proxy_hysteria_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ClientConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClientConfig) ProtoMessage() {}
|
||||
|
||||
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_hysteria_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
|
||||
func (*ClientConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
|
||||
if x != nil {
|
||||
return x.Server
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\"f\n" +
|
||||
"\fClientConfig\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" +
|
||||
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06serverB[\n" +
|
||||
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
file_proxy_hysteria_config_proto_rawDescOnce sync.Once
|
||||
file_proxy_hysteria_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proxy_hysteria_config_proto_rawDescGZIP() []byte {
|
||||
file_proxy_hysteria_config_proto_rawDescOnce.Do(func() {
|
||||
file_proxy_hysteria_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)))
|
||||
})
|
||||
return file_proxy_hysteria_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_proxy_hysteria_config_proto_goTypes = []any{
|
||||
(*ClientConfig)(nil), // 0: xray.proxy.hysteria.ClientConfig
|
||||
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint
|
||||
}
|
||||
var file_proxy_hysteria_config_proto_depIdxs = []int32{
|
||||
1, // 0: xray.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_hysteria_config_proto_init() }
|
||||
func file_proxy_hysteria_config_proto_init() {
|
||||
if File_proxy_hysteria_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proxy_hysteria_config_proto_goTypes,
|
||||
DependencyIndexes: file_proxy_hysteria_config_proto_depIdxs,
|
||||
MessageInfos: file_proxy_hysteria_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proxy_hysteria_config_proto = out.File
|
||||
file_proxy_hysteria_config_proto_goTypes = nil
|
||||
file_proxy_hysteria_config_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.proxy.hysteria;
|
||||
option csharp_namespace = "Xray.Proxy.Hysteria";
|
||||
option go_package = "github.com/xtls/xray-core/proxy/hysteria";
|
||||
option java_package = "com.xray.proxy.hysteria";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/protocol/server_spec.proto";
|
||||
|
||||
message ClientConfig {
|
||||
int32 version = 1;
|
||||
xray.common.protocol.ServerEndpoint server = 2;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const (
|
||||
requireDatagram key = iota
|
||||
)
|
||||
|
||||
func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {
|
||||
if !udp {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, requireDatagram, struct{}{})
|
||||
}
|
||||
|
||||
func RequireDatagramFromContext(ctx context.Context) bool {
|
||||
_, ok := ctx.Value(requireDatagram).(struct{})
|
||||
return ok
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package hysteria
|
||||
|
||||
func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
|
||||
if m.Size() <= maxSize {
|
||||
return []UDPMessage{*m}
|
||||
}
|
||||
fullPayload := m.Data
|
||||
maxPayloadSize := maxSize - m.HeaderSize()
|
||||
off := 0
|
||||
fragID := uint8(0)
|
||||
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
||||
frags := make([]UDPMessage, fragCount)
|
||||
for off < len(fullPayload) {
|
||||
payloadSize := len(fullPayload) - off
|
||||
if payloadSize > maxPayloadSize {
|
||||
payloadSize = maxPayloadSize
|
||||
}
|
||||
frag := *m
|
||||
frag.FragID = fragID
|
||||
frag.FragCount = fragCount
|
||||
frag.Data = fullPayload[off : off+payloadSize]
|
||||
frags[fragID] = frag
|
||||
off += payloadSize
|
||||
fragID++
|
||||
}
|
||||
return frags
|
||||
}
|
||||
|
||||
// Defragger handles the defragmentation of UDP messages.
|
||||
// The current implementation can only handle one packet ID at a time.
|
||||
// If another packet arrives before a packet has received all fragments
|
||||
// in their entirety, any previous state is discarded.
|
||||
type Defragger struct {
|
||||
pktID uint16
|
||||
frags []*UDPMessage
|
||||
count uint8
|
||||
size int // data size
|
||||
}
|
||||
|
||||
func (d *Defragger) Feed(m *UDPMessage) *UDPMessage {
|
||||
if m.FragCount <= 1 {
|
||||
return m
|
||||
}
|
||||
if m.FragID >= m.FragCount {
|
||||
// wtf is this?
|
||||
return nil
|
||||
}
|
||||
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
|
||||
// new message, clear previous state
|
||||
d.pktID = m.PacketID
|
||||
d.frags = make([]*UDPMessage, m.FragCount)
|
||||
d.frags[m.FragID] = m
|
||||
d.count = 1
|
||||
d.size = len(m.Data)
|
||||
} else if d.frags[m.FragID] == nil {
|
||||
d.frags[m.FragID] = m
|
||||
d.count++
|
||||
d.size += len(m.Data)
|
||||
if int(d.count) == len(d.frags) {
|
||||
// all fragments received, assemble
|
||||
data := make([]byte, d.size)
|
||||
off := 0
|
||||
for _, frag := range d.frags {
|
||||
off += copy(data[off:], frag.Data)
|
||||
}
|
||||
m.Data = data
|
||||
m.FragID = 0
|
||||
m.FragCount = 1
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/apernet/quic-go/quicvarint"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
FrameTypeTCPRequest = 0x401
|
||||
|
||||
// Max length values are for preventing DoS attacks
|
||||
|
||||
MaxAddressLength = 2048
|
||||
MaxMessageLength = 2048
|
||||
MaxPaddingLength = 4096
|
||||
|
||||
MaxUDPSize = 4096
|
||||
|
||||
maxVarInt1 = 63
|
||||
maxVarInt2 = 16383
|
||||
maxVarInt4 = 1073741823
|
||||
maxVarInt8 = 4611686018427387903
|
||||
)
|
||||
|
||||
// TCPRequest format:
|
||||
// 0x401 (QUIC varint)
|
||||
// Address length (QUIC varint)
|
||||
// Address (bytes)
|
||||
// Padding length (QUIC varint)
|
||||
// Padding (bytes)
|
||||
|
||||
func WriteTCPRequest(w io.Writer, addr string) error {
|
||||
padding := tcpRequestPadding.String()
|
||||
paddingLen := len(padding)
|
||||
addrLen := len(addr)
|
||||
sz := int(quicvarint.Len(FrameTypeTCPRequest)) +
|
||||
int(quicvarint.Len(uint64(addrLen))) + addrLen +
|
||||
int(quicvarint.Len(uint64(paddingLen))) + paddingLen
|
||||
buf := make([]byte, sz)
|
||||
i := varintPut(buf, FrameTypeTCPRequest)
|
||||
i += varintPut(buf[i:], uint64(addrLen))
|
||||
i += copy(buf[i:], addr)
|
||||
i += varintPut(buf[i:], uint64(paddingLen))
|
||||
copy(buf[i:], padding)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// TCPResponse format:
|
||||
// Status (byte, 0=ok, 1=error)
|
||||
// Message length (QUIC varint)
|
||||
// Message (bytes)
|
||||
// Padding length (QUIC varint)
|
||||
// Padding (bytes)
|
||||
|
||||
func ReadTCPResponse(r io.Reader) (bool, string, error) {
|
||||
var status [1]byte
|
||||
if _, err := io.ReadFull(r, status[:]); err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
bReader := quicvarint.NewReader(r)
|
||||
msgLen, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if msgLen > MaxMessageLength {
|
||||
return false, "", errors.New("invalid message length")
|
||||
}
|
||||
var msgBuf []byte
|
||||
// No message is fine
|
||||
if msgLen > 0 {
|
||||
msgBuf = make([]byte, msgLen)
|
||||
_, err = io.ReadFull(r, msgBuf)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
}
|
||||
paddingLen, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if paddingLen > MaxPaddingLength {
|
||||
return false, "", errors.New("invalid padding length")
|
||||
}
|
||||
if paddingLen > 0 {
|
||||
_, err = io.CopyN(io.Discard, r, int64(paddingLen))
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
}
|
||||
return status[0] == 0, string(msgBuf), nil
|
||||
}
|
||||
|
||||
// UDPMessage format:
|
||||
// Session ID (uint32 BE)
|
||||
// Packet ID (uint16 BE)
|
||||
// Fragment ID (uint8)
|
||||
// Fragment count (uint8)
|
||||
// Address length (QUIC varint)
|
||||
// Address (bytes)
|
||||
// Data...
|
||||
|
||||
type UDPMessage struct {
|
||||
SessionID uint32 // 4
|
||||
PacketID uint16 // 2
|
||||
FragID uint8 // 1
|
||||
FragCount uint8 // 1
|
||||
Addr string // varint + bytes
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (m *UDPMessage) HeaderSize() int {
|
||||
lAddr := len(m.Addr)
|
||||
return 4 + 2 + 1 + 1 + int(quicvarint.Len(uint64(lAddr))) + lAddr
|
||||
}
|
||||
|
||||
func (m *UDPMessage) Size() int {
|
||||
return m.HeaderSize() + len(m.Data)
|
||||
}
|
||||
|
||||
func (m *UDPMessage) Serialize(buf []byte) int {
|
||||
// Make sure the buffer is big enough
|
||||
if len(buf) < m.Size() {
|
||||
return -1
|
||||
}
|
||||
// binary.BigEndian.PutUint32(buf, m.SessionID)
|
||||
binary.BigEndian.PutUint16(buf[4:], m.PacketID)
|
||||
buf[6] = m.FragID
|
||||
buf[7] = m.FragCount
|
||||
i := varintPut(buf[8:], uint64(len(m.Addr)))
|
||||
i += copy(buf[8+i:], m.Addr)
|
||||
i += copy(buf[8+i:], m.Data)
|
||||
return 8 + i
|
||||
}
|
||||
|
||||
func ParseUDPMessage(msg []byte) (*UDPMessage, error) {
|
||||
m := &UDPMessage{}
|
||||
buf := bytes.NewBuffer(msg)
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.SessionID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.PacketID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.FragID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.FragCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lAddr, err := quicvarint.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lAddr == 0 || lAddr > MaxMessageLength {
|
||||
return nil, errors.New("invalid address length")
|
||||
}
|
||||
bs := buf.Bytes()
|
||||
if len(bs) <= int(lAddr) {
|
||||
// We use <= instead of < here as we expect at least one byte of data after the address
|
||||
return nil, errors.New("invalid message length")
|
||||
}
|
||||
m.Addr = string(bs[:lAddr])
|
||||
m.Data = bs[lAddr:]
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// varintPut is like quicvarint.Append, but instead of appending to a slice,
|
||||
// it writes to a fixed-size buffer. Returns the number of bytes written.
|
||||
func varintPut(b []byte, i uint64) int {
|
||||
if i <= maxVarInt1 {
|
||||
b[0] = uint8(i)
|
||||
return 1
|
||||
}
|
||||
if i <= maxVarInt2 {
|
||||
b[0] = uint8(i>>8) | 0x40
|
||||
b[1] = uint8(i)
|
||||
return 2
|
||||
}
|
||||
if i <= maxVarInt4 {
|
||||
b[0] = uint8(i>>24) | 0x80
|
||||
b[1] = uint8(i >> 16)
|
||||
b[2] = uint8(i >> 8)
|
||||
b[3] = uint8(i)
|
||||
return 4
|
||||
}
|
||||
if i <= maxVarInt8 {
|
||||
b[0] = uint8(i>>56) | 0xc0
|
||||
b[1] = uint8(i >> 48)
|
||||
b[2] = uint8(i >> 40)
|
||||
b[3] = uint8(i >> 32)
|
||||
b[4] = uint8(i >> 24)
|
||||
b[5] = uint8(i >> 16)
|
||||
b[6] = uint8(i >> 8)
|
||||
b[7] = uint8(i)
|
||||
return 8
|
||||
}
|
||||
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"io"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/antireplay"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -24,8 +24,6 @@ type MemoryAccount struct {
|
||||
CipherType CipherType
|
||||
Key []byte
|
||||
Password string
|
||||
|
||||
replayFilter antireplay.GeneralizedReplayFilter
|
||||
}
|
||||
|
||||
var ErrIVNotUnique = errors.New("IV is not unique")
|
||||
@@ -42,20 +40,9 @@ func (a *MemoryAccount) ToProto() proto.Message {
|
||||
return &Account{
|
||||
CipherType: a.CipherType,
|
||||
Password: a.Password,
|
||||
IvCheck: a.replayFilter != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *MemoryAccount) CheckIV(iv []byte) error {
|
||||
if a.replayFilter == nil {
|
||||
return nil
|
||||
}
|
||||
if a.replayFilter.Check(iv) {
|
||||
return nil
|
||||
}
|
||||
return ErrIVNotUnique
|
||||
}
|
||||
|
||||
func createAesGcm(key []byte) cipher.AEAD {
|
||||
return crypto.NewAesGcm(key)
|
||||
}
|
||||
@@ -116,12 +103,6 @@ func (a *Account) AsAccount() (protocol.Account, error) {
|
||||
CipherType: a.CipherType,
|
||||
Key: passwordToCipherKey([]byte(a.Password), Cipher.KeySize()),
|
||||
Password: a.Password,
|
||||
replayFilter: func() antireplay.GeneralizedReplayFilter {
|
||||
if a.IvCheck {
|
||||
return antireplay.NewBloomRing()
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -139,9 +139,6 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri
|
||||
if account.Cipher.IVSize() > 0 {
|
||||
iv = make([]byte, account.Cipher.IVSize())
|
||||
common.Must2(rand.Read(iv))
|
||||
if ivError := account.CheckIV(iv); ivError != nil {
|
||||
return nil, errors.New("failed to mark outgoing iv").Base(ivError)
|
||||
}
|
||||
if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
|
||||
return nil, errors.New("failed to write IV")
|
||||
}
|
||||
@@ -188,10 +185,6 @@ func ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, e
|
||||
}
|
||||
}
|
||||
|
||||
if ivError := account.CheckIV(iv); ivError != nil {
|
||||
return nil, drain.WithError(drainer, reader, errors.New("failed iv check").Base(ivError))
|
||||
}
|
||||
|
||||
return account.Cipher.NewDecryptionReader(account.Key, iv, reader)
|
||||
}
|
||||
|
||||
@@ -203,9 +196,6 @@ func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Wr
|
||||
if account.Cipher.IVSize() > 0 {
|
||||
iv = make([]byte, account.Cipher.IVSize())
|
||||
common.Must2(rand.Read(iv))
|
||||
if ivError := account.CheckIV(iv); ivError != nil {
|
||||
return nil, errors.New("failed to mark outgoing iv").Base(ivError)
|
||||
}
|
||||
if err := buf.WriteAllBytes(writer, iv, nil); err != nil {
|
||||
return nil, errors.New("failed to write IV.").Base(err)
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol
|
||||
|
||||
if matchErr == nil {
|
||||
u = user
|
||||
err = account.CheckIV(iv)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
+59
-1
@@ -4,7 +4,7 @@ TUN interface support bridges the gap between network layer 3 and layer 7, intro
|
||||
|
||||
This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \
|
||||
Primary targets are Linux based router devices. Like most popular OpenWRT option. \
|
||||
Although support for Windows is also implemented (see below).
|
||||
Support for Windows, macOS, Android and iOS is also implemented (see below).
|
||||
|
||||
## PLEASE READ FOLLOWING CAREFULLY
|
||||
|
||||
@@ -172,3 +172,61 @@ route add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47
|
||||
Note on ipv6 support. \
|
||||
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
|
||||
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine
|
||||
|
||||
## MAC OS X SUPPORT
|
||||
|
||||
Darwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).
|
||||
|
||||
Interface name in the configuration must comply to the scheme "utunN", where N is some number. \
|
||||
Most running OS'es create some amount of utun interfaces in advance for own needs. Please either check the interfaces you already have occupied by issuing following command:
|
||||
```
|
||||
ifconfig
|
||||
```
|
||||
Produced list will have all system interfaces listed, from which you will see how many "utun" ones already exists.
|
||||
It's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have "utun8" in the config. You can choose any available name, even utun20, to get surely available interface number.
|
||||
|
||||
To attach routing to the interface, route command like following can be executed:
|
||||
```
|
||||
sudo route add -net 1.1.1.0/24 -iface utun10
|
||||
```
|
||||
```
|
||||
sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10
|
||||
sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10
|
||||
```
|
||||
Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.
|
||||
|
||||
## ANDROID SUPPORT
|
||||
|
||||
Android uses the VpnService API which provides a TUN file descriptor to the application.
|
||||
|
||||
Obtain the fd from VpnService:
|
||||
```kotlin
|
||||
val tunFd = vpnInterface.fd
|
||||
```
|
||||
|
||||
Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Kotlin/Java or by exposing a Go function via gomobile bindings.
|
||||
|
||||
Build using gomobile for Android library integration:
|
||||
```
|
||||
gomobile bind -target=android
|
||||
```
|
||||
|
||||
## iOS SUPPORT
|
||||
|
||||
iOS uses the same utun packet format as macOS, but the file descriptor is provided by NetworkExtension.
|
||||
|
||||
Obtain the fd from NetworkExtension:
|
||||
```swift
|
||||
var buf = [CChar](repeating: 0, count: Int(IFNAMSIZ))
|
||||
let utunPrefix = "utun".utf8CString.dropLast()
|
||||
let tunFd = ((0 ... 1024).first { (_ fd: Int32) -> Bool in var len = socklen_t(buf.count)
|
||||
return getsockopt(fd, 2, 2, &buf, &len) == 0 && buf.starts(with: utunPrefix)
|
||||
}!
|
||||
```
|
||||
|
||||
Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Swift/Objective-C or by exposing a Go function via gomobile bindings.
|
||||
|
||||
Build using gomobile for iOS framework integration:
|
||||
```
|
||||
gomobile bind -target=ios
|
||||
```
|
||||
+43
-17
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
@@ -19,11 +20,13 @@ import (
|
||||
|
||||
// Handler is managing object that tie together tun interface, ip stack and dispatch connections to the routing
|
||||
type Handler struct {
|
||||
ctx context.Context
|
||||
config *Config
|
||||
stack Stack
|
||||
policyManager policy.Manager
|
||||
dispatcher routing.Dispatcher
|
||||
ctx context.Context
|
||||
config *Config
|
||||
stack Stack
|
||||
policyManager policy.Manager
|
||||
dispatcher routing.Dispatcher
|
||||
tag string
|
||||
sniffingRequest session.SniffingRequest
|
||||
}
|
||||
|
||||
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
||||
@@ -43,6 +46,14 @@ func (t *Handler) policy() policy.Session {
|
||||
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
|
||||
var err error
|
||||
|
||||
// Retrieve tag and sniffing config from context (set by AlwaysOnInboundHandler)
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
t.tag = inbound.Tag
|
||||
}
|
||||
if content := session.ContentFromContext(ctx); content != nil {
|
||||
t.sniffingRequest = content.SniffingRequest
|
||||
}
|
||||
|
||||
t.ctx = core.ToBackgroundDetachedContext(ctx)
|
||||
t.policyManager = pm
|
||||
t.dispatcher = dispatcher
|
||||
@@ -91,31 +102,46 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
|
||||
|
||||
// HandleConnection pass the connection coming from the ip stack to the routing dispatcher
|
||||
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
||||
sid := session.NewID()
|
||||
ctx := c.ContextWithID(t.ctx, sid)
|
||||
errors.LogInfo(ctx, "processing connection from: ", conn.RemoteAddr())
|
||||
// when handling is done with any outcome, always signal back to the incoming connection
|
||||
// to close, send completion packets back to the network, and cleanup
|
||||
defer conn.Close()
|
||||
|
||||
inbound := session.Inbound{}
|
||||
inbound.Name = "tun"
|
||||
inbound.CanSpliceCopy = 1
|
||||
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
||||
inbound.User = &protocol.MemoryUser{
|
||||
Level: t.config.UserLevel,
|
||||
ctx, cancel := context.WithCancel(t.ctx)
|
||||
defer cancel()
|
||||
ctx = c.ContextWithID(ctx, session.NewID())
|
||||
|
||||
source := net.DestinationFromAddr(conn.RemoteAddr())
|
||||
inbound := session.Inbound{
|
||||
Name: "tun",
|
||||
Tag: t.tag,
|
||||
CanSpliceCopy: 3,
|
||||
Source: source,
|
||||
User: &protocol.MemoryUser{
|
||||
Level: t.config.UserLevel,
|
||||
},
|
||||
}
|
||||
|
||||
ctx = session.ContextWithInbound(ctx, &inbound)
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: t.sniffingRequest,
|
||||
})
|
||||
ctx = session.SubContextFromMuxInbound(ctx)
|
||||
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: destination,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
})
|
||||
errors.LogInfo(ctx, "processing from ", source, " to ", destination)
|
||||
|
||||
link := &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||
Writer: buf.NewWriter(conn),
|
||||
}
|
||||
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||
return
|
||||
}
|
||||
|
||||
errors.LogInfo(ctx, "connection completed")
|
||||
}
|
||||
|
||||
// Network implements proxy.Inbound
|
||||
|
||||
+82
-24
@@ -6,8 +6,10 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
@@ -100,32 +102,21 @@ func (t *stackGVisor) Start() error {
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||
|
||||
udpForwarder := udp.NewForwarder(ipStack, func(r *udp.ForwarderRequest) {
|
||||
go func(r *udp.ForwarderRequest) {
|
||||
var wq waiter.Queue
|
||||
var id = r.ID()
|
||||
// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support
|
||||
udpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket)
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||
data := pkt.Data().AsRange().ToSlice()
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
// source/destination of the packet we process as incoming, on gVisor side are Remote/Local
|
||||
// in other terms, src is the side behind tun, dst is the side behind gVisor
|
||||
// this function handle packets passing from the tun to the gVisor, therefore the src/dst assignement
|
||||
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
|
||||
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
|
||||
|
||||
ep, err := r.CreateEndpoint(&wq)
|
||||
if err != nil {
|
||||
errors.LogError(t.ctx, err.String())
|
||||
return
|
||||
}
|
||||
|
||||
options := ep.SocketOptions()
|
||||
options.SetReuseAddress(true)
|
||||
options.SetReusePort(true)
|
||||
|
||||
t.handler.HandleConnection(
|
||||
gonet.NewUDPConn(&wq, ep),
|
||||
// local address on the gVisor side is connection destination
|
||||
net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
|
||||
)
|
||||
|
||||
// close the socket
|
||||
ep.Close()
|
||||
}(r)
|
||||
return udpForwarder.HandlePacket(src, dst, data)
|
||||
})
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
||||
|
||||
t.stack = ipStack
|
||||
t.endpoint = linkEndpoint
|
||||
@@ -133,6 +124,69 @@ func (t *stackGVisor) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *stackGVisor) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
|
||||
udpLen := header.UDPMinimumSize + len(payload)
|
||||
srcIP := tcpip.AddrFromSlice(src.Address.IP())
|
||||
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
|
||||
|
||||
// build packet with appropriate IP header size
|
||||
isIPv4 := dst.Address.Family().IsIPv4()
|
||||
ipHdrSize := header.IPv6MinimumSize
|
||||
ipProtocol := header.IPv6ProtocolNumber
|
||||
if isIPv4 {
|
||||
ipHdrSize = header.IPv4MinimumSize
|
||||
ipProtocol = header.IPv4ProtocolNumber
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
|
||||
Payload: buffer.MakeWithData(payload),
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
|
||||
// Build UDP header
|
||||
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
|
||||
udpHdr.Encode(&header.UDPFields{
|
||||
SrcPort: uint16(src.Port),
|
||||
DstPort: uint16(dst.Port),
|
||||
Length: uint16(udpLen),
|
||||
})
|
||||
|
||||
// Calculate and set UDP checksum
|
||||
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
|
||||
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
||||
|
||||
// Build IP header
|
||||
if isIPv4 {
|
||||
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
||||
ipHdr.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
|
||||
TTL: 64,
|
||||
Protocol: uint8(header.UDPProtocolNumber),
|
||||
SrcAddr: srcIP,
|
||||
DstAddr: dstIP,
|
||||
})
|
||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||
} else {
|
||||
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
||||
ipHdr.Encode(&header.IPv6Fields{
|
||||
PayloadLength: uint16(udpLen),
|
||||
TransportProtocol: header.UDPProtocolNumber,
|
||||
HopLimit: 64,
|
||||
SrcAddr: srcIP,
|
||||
DstAddr: dstIP,
|
||||
})
|
||||
}
|
||||
|
||||
// dispatch the packet
|
||||
err := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView()))
|
||||
if err != nil {
|
||||
return errors.New("failed to write raw udp packet back to stack", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is called by Handler to shut down the stack
|
||||
func (t *stackGVisor) Close() error {
|
||||
if t.stack == nil {
|
||||
@@ -182,6 +236,10 @@ func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
|
||||
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
|
||||
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
|
||||
|
||||
// Disable RACK/TLP loss recovery to fix connection stalls under high load
|
||||
rOpt := tcpip.TCPRecovery(0)
|
||||
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &rOpt)
|
||||
|
||||
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
|
||||
Min: tcpRXBufMinSize,
|
||||
Default: tcpRXBufDefSize,
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var ErrQueueEmpty = errors.New("queue is empty")
|
||||
|
||||
type GVisorDevice interface {
|
||||
WritePacket(packet *stack.PacketBuffer) tcpip.Error
|
||||
ReadPacket() (byte, *stack.PacketBuffer, error)
|
||||
Wait()
|
||||
}
|
||||
|
||||
// LinkEndpoint implements GVisor stack.LinkEndpoint
|
||||
var _ stack.LinkEndpoint = (*LinkEndpoint)(nil)
|
||||
|
||||
type LinkEndpoint struct {
|
||||
deviceMTU uint32
|
||||
device GVisorDevice
|
||||
dispatcherCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) MTU() uint32 {
|
||||
return e.deviceMTU
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) SetMTU(_ uint32) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
|
||||
if dispatcher != nil {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go e.dispatchLoop(ctx, dispatcher)
|
||||
e.dispatcherCancel = cancel
|
||||
}
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) IsAttached() bool {
|
||||
return e.dispatcherCancel != nil
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) Wait() {
|
||||
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||
// tun interface doesn't have link layer header, it will be added by the OS
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) Close() {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) SetOnCloseAction(_ func()) {
|
||||
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
var n int
|
||||
var err tcpip.Error
|
||||
|
||||
for _, packetBuffer := range packetBufferList.AsSlice() {
|
||||
err = e.device.WritePacket(packetBuffer)
|
||||
if err != nil {
|
||||
return n, &tcpip.ErrAborted{}
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *LinkEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
|
||||
var networkProtocolNumber tcpip.NetworkProtocolNumber
|
||||
var version byte
|
||||
var packet *stack.PacketBuffer
|
||||
var err error
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
version, packet, err = e.device.ReadPacket()
|
||||
// on "queue empty", ask device to yield slightly and continue
|
||||
if errors.Is(err, ErrQueueEmpty) {
|
||||
e.device.Wait()
|
||||
continue
|
||||
}
|
||||
// stop dispatcher loop on any other interface failure
|
||||
if err != nil {
|
||||
e.Attach(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// extract network protocol number from the packet first byte
|
||||
// (which is returned separately, since it is so incredibly hard to extract one byte from
|
||||
// stack.PacketBuffer without additional memory allocation and full copying it back and forth)
|
||||
switch version {
|
||||
case 4:
|
||||
networkProtocolNumber = header.IPv4ProtocolNumber
|
||||
case 6:
|
||||
networkProtocolNumber = header.IPv6ProtocolNumber
|
||||
default:
|
||||
// discard unknown network protocol packet
|
||||
packet.DecRef()
|
||||
continue
|
||||
}
|
||||
|
||||
// dispatch the buffer to the stack
|
||||
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
|
||||
// signal the buffer that it can be released
|
||||
packet.DecRef()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
//go:build darwin
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
utunControlName = "com.apple.net.utun_control"
|
||||
sysprotoControl = 2
|
||||
gateway = "169.254.10.1/30"
|
||||
utunHeaderSize = 4
|
||||
)
|
||||
|
||||
const (
|
||||
SIOCAIFADDR6 = 2155899162 // netinet6/in6_var.h
|
||||
IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h
|
||||
IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h
|
||||
ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
||||
)
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
|
||||
type DarwinTun struct {
|
||||
tunFile *os.File
|
||||
options TunOptions
|
||||
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
|
||||
}
|
||||
|
||||
var _ Tun = (*DarwinTun)(nil)
|
||||
var _ GVisorTun = (*DarwinTun)(nil)
|
||||
var _ GVisorDevice = (*DarwinTun)(nil)
|
||||
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
// Check if fd is provided via environment (iOS mode)
|
||||
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
|
||||
if fdStr != "" {
|
||||
// iOS: use provided fd from NetworkExtension
|
||||
fd, err := strconv.Atoi(fdStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = unix.SetNonblock(fd, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DarwinTun{
|
||||
tunFile: os.NewFile(uintptr(fd), "utun"),
|
||||
options: options,
|
||||
ownsFd: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// macOS: create our own utun interface
|
||||
tunFile, err := open(options.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setup(options.Name, options.MTU)
|
||||
if err != nil {
|
||||
_ = tunFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DarwinTun{
|
||||
tunFile: tunFile,
|
||||
options: options,
|
||||
ownsFd: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DarwinTun) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DarwinTun) Close() error {
|
||||
if t.ownsFd {
|
||||
return t.tunFile.Close()
|
||||
}
|
||||
// iOS: don't close the fd, it's owned by NetworkExtension
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
||||
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
||||
defer b.Release()
|
||||
|
||||
// prepare Darwin specific packet header
|
||||
_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})
|
||||
// copy the bytes of slices that compose the packet into the allocated buffer
|
||||
for _, packetElement := range packet.AsSlices() {
|
||||
_, _ = b.Write(packetElement)
|
||||
}
|
||||
// fill Darwin specific header from the first raw packet byte, that we can access now
|
||||
var family byte
|
||||
switch b.Byte(4) >> 4 {
|
||||
case 4:
|
||||
family = unix.AF_INET
|
||||
case 6:
|
||||
family = unix.AF_INET6
|
||||
default:
|
||||
return &tcpip.ErrAborted{}
|
||||
}
|
||||
b.SetByte(3, family)
|
||||
|
||||
if _, err := t.tunFile.Write(b.Bytes()); err != nil {
|
||||
if errors.Is(err, unix.EAGAIN) {
|
||||
return &tcpip.ErrWouldBlock{}
|
||||
}
|
||||
return &tcpip.ErrAborted{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPacket implements GVisorDevice method to read one packet from the tun device
|
||||
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
||||
// which will make the stack call Wait which should implement desired push-back
|
||||
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
||||
|
||||
// read the bytes to the interface file
|
||||
n, err := b.ReadFrom(t.tunFile)
|
||||
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
|
||||
b.Release()
|
||||
return 0, nil, ErrQueueEmpty
|
||||
}
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// discard empty or sub-empty packets
|
||||
if n <= utunHeaderSize {
|
||||
b.Release()
|
||||
return 0, nil, ErrQueueEmpty
|
||||
}
|
||||
|
||||
// network protocol version from first byte of the raw packet, the one that follows Darwin specific header
|
||||
version := b.Byte(utunHeaderSize) >> 4
|
||||
packetBuffer := buffer.MakeWithData(b.BytesFrom(utunHeaderSize))
|
||||
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: packetBuffer,
|
||||
IsForwardedPacket: true,
|
||||
OnRelease: func() {
|
||||
b.Release()
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Wait some cpu cycles
|
||||
func (t *DarwinTun) Wait() {
|
||||
procyield(1)
|
||||
}
|
||||
|
||||
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
||||
}
|
||||
|
||||
// open the interface, by creating new utunN if in the system and returning its file descriptor
|
||||
func open(name string) (*os.File, error) {
|
||||
ifIndex := -1
|
||||
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
|
||||
if err != nil || ifIndex < 0 {
|
||||
return nil, errors.New("interface name must be utunN, where N is a number, e.g. utun9, utun11 and so on")
|
||||
}
|
||||
|
||||
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctlInfo := &unix.CtlInfo{}
|
||||
copy(ctlInfo.Name[:], utunControlName)
|
||||
if err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sockaddr := &unix.SockaddrCtl{
|
||||
ID: ctlInfo.Id,
|
||||
Unit: uint32(ifIndex) + 1,
|
||||
}
|
||||
if err := unix.Connect(fd, sockaddr); err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := unix.SetNonblock(fd, true); err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.NewFile(uintptr(fd), name), nil
|
||||
}
|
||||
|
||||
// setup the interface by name
|
||||
func setup(name string, MTU uint32) error {
|
||||
if err := setMTU(name, MTU); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
* Darwin routing require tunnel type interface to have local and remote address, to be routable.
|
||||
* To simplify inevitable task, assign the interface static ip address, which in current implementation
|
||||
* is just some random ip from link-local pool, allowing to not bother about existing routing intersection.
|
||||
*/
|
||||
syntheticIP, _ := netip.ParsePrefix(gateway)
|
||||
if err := setIPAddress(name, syntheticIP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setMTU sets MTU on the interface by given name
|
||||
func setMTU(name string, mtu uint32) error {
|
||||
socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socket)
|
||||
|
||||
ifr := unix.IfreqMTU{MTU: int32(mtu)}
|
||||
copy(ifr.Name[:], name)
|
||||
return unix.IoctlSetIfreqMTU(socket, &ifr)
|
||||
}
|
||||
|
||||
type ifAliasReq4 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet4
|
||||
Dstaddr unix.RawSockaddrInet4
|
||||
Mask unix.RawSockaddrInet4
|
||||
}
|
||||
|
||||
type ifAliasReq6 struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet6
|
||||
Dstaddr unix.RawSockaddrInet6
|
||||
Mask unix.RawSockaddrInet6
|
||||
Flags uint32
|
||||
Lifetime addrLifetime6
|
||||
}
|
||||
|
||||
type addrLifetime6 struct {
|
||||
Expire float64
|
||||
Preferred float64
|
||||
Vltime uint32
|
||||
Pltime uint32
|
||||
}
|
||||
|
||||
// setIPAddress sets ipv4 and ipv6 addresses to the interface, required for the routing to work
|
||||
func setIPAddress(name string, gateway netip.Prefix) error {
|
||||
socket4, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socket4)
|
||||
|
||||
// assume local ip address is next one from the remote address
|
||||
local4 := gateway.Addr().As4()
|
||||
local4[3]++
|
||||
|
||||
// fill the configuration for ipv4
|
||||
ifReq4 := ifAliasReq4{
|
||||
Addr: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: local4,
|
||||
},
|
||||
Dstaddr: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: gateway.Addr().As4(),
|
||||
},
|
||||
Mask: unix.RawSockaddrInet4{
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Family: unix.AF_INET,
|
||||
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(gateway.Bits(), 32)).String()).As4(),
|
||||
},
|
||||
}
|
||||
copy(ifReq4.Name[:], name)
|
||||
if err = ioctlPtr(socket4, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq4)); err != nil {
|
||||
return os.NewSyscallError("SIOCAIFADDR", err)
|
||||
}
|
||||
|
||||
socket6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socket6)
|
||||
|
||||
// link-local ipv6 address with suffix from ipv6
|
||||
local6 := netip.AddrFrom16([16]byte{0: 0xfe, 1: 0x80, 12: local4[0], 13: local4[1], 14: local4[2], 15: local4[3]})
|
||||
|
||||
// fill the configuration for ipv6
|
||||
// only link-local address without the destination is enough for it
|
||||
ifReq6 := ifAliasReq6{
|
||||
Addr: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: local6.As16(),
|
||||
},
|
||||
Mask: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(64, 128)).String()).As16(),
|
||||
},
|
||||
Flags: IN6_IFF_NODAD,
|
||||
Lifetime: addrLifetime6{
|
||||
Vltime: ND6_INFINITE_LIFETIME,
|
||||
Pltime: ND6_INFINITE_LIFETIME,
|
||||
},
|
||||
}
|
||||
// assign link-local ipv6 address to the interface.
|
||||
// this will additionally trigger OS level autoconfiguration, which will result two different link-local
|
||||
// addresses - the requested one, and autoconfigured one.
|
||||
// this really has no known side effects, just look excessive. and actually considered pretty normal way to
|
||||
// enable the ipv6 on the interface by macOS concepts.
|
||||
copy(ifReq6.Name[:], name)
|
||||
if err = ioctlPtr(socket6, SIOCAIFADDR6, unsafe.Pointer(&ifReq6)); err != nil {
|
||||
return os.NewSyscallError("SIOCAIFADDR6", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
||||
_, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux && !windows && !android
|
||||
//go:build !linux && !windows && !android && !darwin
|
||||
|
||||
package tun
|
||||
|
||||
|
||||
+70
-10
@@ -3,19 +3,28 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wintun"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
|
||||
// WindowsTun is an object that handles tun network interface on Windows
|
||||
// current version is heavily stripped to do nothing more,
|
||||
// then create a network interface, to be provided as endpoint to gVisor ip stack
|
||||
type WindowsTun struct {
|
||||
options TunOptions
|
||||
adapter *wintun.Adapter
|
||||
session wintun.Session
|
||||
MTU uint32
|
||||
options TunOptions
|
||||
adapter *wintun.Adapter
|
||||
session wintun.Session
|
||||
readWait windows.Handle
|
||||
MTU uint32
|
||||
}
|
||||
|
||||
// WindowsTun implements Tun
|
||||
@@ -24,6 +33,9 @@ var _ Tun = (*WindowsTun)(nil)
|
||||
// WindowsTun implements GVisorTun
|
||||
var _ GVisorTun = (*WindowsTun)(nil)
|
||||
|
||||
// WindowsTun implements GVisorDevice
|
||||
var _ GVisorDevice = (*WindowsTun)(nil)
|
||||
|
||||
// NewTun creates a Wintun interface with the given name. Should a Wintun
|
||||
// interface with the same name exist, it tried to be reused.
|
||||
func NewTun(options TunOptions) (Tun, error) {
|
||||
@@ -41,9 +53,10 @@ func NewTun(options TunOptions) (Tun, error) {
|
||||
}
|
||||
|
||||
tun := &WindowsTun{
|
||||
options: options,
|
||||
adapter: adapter,
|
||||
session: session,
|
||||
options: options,
|
||||
adapter: adapter,
|
||||
session: session,
|
||||
readWait: session.ReadWaitEvent(),
|
||||
// there is currently no iphndl.dll support, which is the netlink library for windows
|
||||
// so there is nowhere to change MTU for the Wintun interface, and we take its default value
|
||||
MTU: wintun.PacketSizeMax,
|
||||
@@ -78,7 +91,54 @@ func (t *WindowsTun) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newEndpoint builds new gVisor stack.LinkEndpoint (WintunEndpoint) on top of WindowsTun
|
||||
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &WintunEndpoint{tun: t}, nil
|
||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
||||
func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
|
||||
// request buffer from Wintun
|
||||
packet, err := t.session.AllocateSendPacket(packetBuffer.Size())
|
||||
if err != nil {
|
||||
return &tcpip.ErrAborted{}
|
||||
}
|
||||
|
||||
// copy the bytes of slices that compose the packet into the allocated buffer
|
||||
var index int
|
||||
for _, packetElement := range packetBuffer.AsSlices() {
|
||||
index += copy(packet[index:], packetElement)
|
||||
}
|
||||
|
||||
// signal Wintun to send that buffer as the packet
|
||||
t.session.SendPacket(packet)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPacket implements GVisorDevice method to read one packet from the tun device
|
||||
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
|
||||
// which will make the stack call Wait which should implement desired push-back
|
||||
func (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
||||
packet, err := t.session.ReceivePacket()
|
||||
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
||||
return 0, nil, ErrQueueEmpty
|
||||
}
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
version := packet[0] >> 4
|
||||
packetBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))
|
||||
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: packetBuffer,
|
||||
IsForwardedPacket: true,
|
||||
OnRelease: func() {
|
||||
t.session.ReleaseReceivePacket(packet)
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (t *WindowsTun) Wait() {
|
||||
procyield(1)
|
||||
_, _ = windows.WaitForSingleObject(t.readWait, windows.INFINITE)
|
||||
}
|
||||
|
||||
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// WintunEndpoint implements GVisor stack.LinkEndpoint
|
||||
var _ stack.LinkEndpoint = (*WintunEndpoint)(nil)
|
||||
|
||||
type WintunEndpoint struct {
|
||||
tun *WindowsTun
|
||||
dispatcherCancel context.CancelFunc
|
||||
}
|
||||
|
||||
var ErrUnsupportedNetworkProtocol = errors.New("unsupported ip version")
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
|
||||
func (e *WintunEndpoint) MTU() uint32 {
|
||||
return e.tun.MTU
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetMTU(mtu uint32) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
|
||||
if dispatcher != nil {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go e.dispatchLoop(ctx, dispatcher)
|
||||
e.dispatcherCancel = cancel
|
||||
}
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) IsAttached() bool {
|
||||
return e.dispatcherCancel != nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Wait() {
|
||||
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||
// tun interface doesn't have link layer header, it will be added by the OS
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) Close() {
|
||||
if e.dispatcherCancel != nil {
|
||||
e.dispatcherCancel()
|
||||
e.dispatcherCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) SetOnCloseAction(f func()) {
|
||||
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
var n int
|
||||
// for all packets in the list to send
|
||||
for _, packetBuffer := range packetBufferList.AsSlice() {
|
||||
// request buffer from Wintun
|
||||
packet, err := e.tun.session.AllocateSendPacket(packetBuffer.Size())
|
||||
if err != nil {
|
||||
return n, &tcpip.ErrAborted{}
|
||||
}
|
||||
|
||||
// copy the bytes of slices that compose the packet into the allocated buffer
|
||||
var index int
|
||||
for _, packetElement := range packetBuffer.AsSlices() {
|
||||
index += copy(packet[index:], packetElement)
|
||||
}
|
||||
|
||||
// signal Wintun to send that buffer as the packet
|
||||
e.tun.session.SendPacket(packet)
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) readPacket() (tcpip.NetworkProtocolNumber, *stack.PacketBuffer, error) {
|
||||
packet, err := e.tun.session.ReceivePacket()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var networkProtocol tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
networkProtocol = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
networkProtocol = header.IPv6ProtocolNumber
|
||||
default:
|
||||
e.tun.session.ReleaseReceivePacket(packet)
|
||||
return 0, nil, ErrUnsupportedNetworkProtocol
|
||||
}
|
||||
|
||||
packetBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: packetBuffer,
|
||||
IsForwardedPacket: true,
|
||||
OnRelease: func() {
|
||||
e.tun.session.ReleaseReceivePacket(packet)
|
||||
},
|
||||
})
|
||||
return networkProtocol, pkt, nil
|
||||
}
|
||||
|
||||
func (e *WintunEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
|
||||
readWait := e.tun.session.ReadWaitEvent()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
networkProtocolNumber, packet, err := e.readPacket()
|
||||
// read queue empty, yield slightly, wait for the spinlock, retry
|
||||
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
||||
procyield(1)
|
||||
_, _ = windows.WaitForSingleObject(readWait, windows.INFINITE)
|
||||
continue
|
||||
}
|
||||
// discard unknown network protocol packet
|
||||
if errors.Is(err, ErrUnsupportedNetworkProtocol) {
|
||||
continue
|
||||
}
|
||||
// stop dispatcher loop on any other interface failure
|
||||
if err != nil {
|
||||
e.Attach(nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// dispatch the buffer to the stack
|
||||
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
|
||||
// signal the buffer that it can be released
|
||||
packet.DecRef()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
// sub-handler specifically for udp connections under main handler
|
||||
type udpConnectionHandler struct {
|
||||
sync.Mutex
|
||||
|
||||
udpConns map[net.Destination]*udpConn
|
||||
|
||||
handleConnection func(conn net.Conn, dest net.Destination)
|
||||
writePacket func(data []byte, src net.Destination, dst net.Destination) error
|
||||
}
|
||||
|
||||
func newUdpConnectionHandler(handleConnection func(conn net.Conn, dest net.Destination), writePacket func(data []byte, src net.Destination, dst net.Destination) error) *udpConnectionHandler {
|
||||
handler := &udpConnectionHandler{
|
||||
udpConns: make(map[net.Destination]*udpConn),
|
||||
handleConnection: handleConnection,
|
||||
writePacket: writePacket,
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// HandlePacket handles UDP packets coming from tun, to forward to the dispatcher
|
||||
// this custom handler support FullCone NAT of returning packets, binding connection only by the source addr:port
|
||||
func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destination, data []byte) bool {
|
||||
u.Lock()
|
||||
conn, found := u.udpConns[src]
|
||||
if !found {
|
||||
egress := make(chan []byte, 16)
|
||||
conn = &udpConn{handler: u, egress: egress, src: src, dst: dst}
|
||||
u.udpConns[src] = conn
|
||||
|
||||
go u.handleConnection(conn, dst)
|
||||
}
|
||||
u.Unlock()
|
||||
|
||||
// send packet data to the egress channel, if it has buffer, or discard
|
||||
select {
|
||||
case conn.egress <- data:
|
||||
default:
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *udpConnectionHandler) connectionFinished(src net.Destination) {
|
||||
u.Lock()
|
||||
conn, found := u.udpConns[src]
|
||||
if found {
|
||||
delete(u.udpConns, src)
|
||||
close(conn.egress)
|
||||
}
|
||||
u.Unlock()
|
||||
}
|
||||
|
||||
// udp connection abstraction
|
||||
type udpConn struct {
|
||||
net.Conn
|
||||
buf.Writer
|
||||
|
||||
handler *udpConnectionHandler
|
||||
|
||||
egress chan []byte
|
||||
src net.Destination
|
||||
dst net.Destination
|
||||
}
|
||||
|
||||
// Read packets from the connection
|
||||
func (c *udpConn) Read(p []byte) (int, error) {
|
||||
data, ok := <-c.egress
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n := copy(p, data)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write returning packets back
|
||||
func (c *udpConn) Write(p []byte) (int, error) {
|
||||
// sending packets back mean sending payload with source/destination reversed
|
||||
err := c.handler.writePacket(p, c.dst, c.src)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *udpConn) Close() error {
|
||||
c.handler.connectionFinished(c.src)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpConn) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{IP: c.dst.Address.IP(), Port: int(c.dst.Port.Value())}
|
||||
}
|
||||
|
||||
func (c *udpConn) RemoteAddr() net.Addr {
|
||||
return &net.UDPAddr{IP: c.src.Address.IP(), Port: int(c.src.Port.Value())}
|
||||
}
|
||||
|
||||
// Write returning packets back
|
||||
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
for _, b := range mb {
|
||||
dst := c.dst
|
||||
if b.UDP != nil {
|
||||
dst = *b.UDP
|
||||
}
|
||||
|
||||
// validate address family matches between buffer packet and the connection
|
||||
if dst.Address.Family() != c.dst.Address.Family() {
|
||||
continue
|
||||
}
|
||||
|
||||
// sending packets back mean sending payload with source/destination reversed
|
||||
err := c.handler.writePacket(b.Bytes(), dst, c.src)
|
||||
if err != nil {
|
||||
// udp doesn't guarantee delivery, so in any failure we just continue to the next packet
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -68,12 +68,12 @@ func (aidd *AuthIDDecoder) Decode(data [16]byte) (int64, uint32, int32, []byte)
|
||||
}
|
||||
|
||||
func NewAuthIDDecoderHolder() *AuthIDDecoderHolder {
|
||||
return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewReplayFilter(120)}
|
||||
return &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewMapFilter[[16]byte](120)}
|
||||
}
|
||||
|
||||
type AuthIDDecoderHolder struct {
|
||||
decoders map[string]*AuthIDDecoderItem
|
||||
filter *antireplay.ReplayFilter
|
||||
filter *antireplay.ReplayFilter[[16]byte]
|
||||
}
|
||||
|
||||
type AuthIDDecoderItem struct {
|
||||
@@ -111,7 +111,7 @@ func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
|
||||
return nil, ErrInvalidTime
|
||||
}
|
||||
|
||||
if !a.filter.Check(authID[:]) {
|
||||
if !a.filter.Check(authID) {
|
||||
return nil, ErrReplay
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
|
||||
})
|
||||
stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||
|
||||
udpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) {
|
||||
udpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) bool {
|
||||
go func(r *udp.ForwarderRequest) {
|
||||
var (
|
||||
wq waiter.Queue
|
||||
@@ -195,6 +195,8 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
|
||||
|
||||
handler(net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewUDPConn(&wq, ep))
|
||||
}(r)
|
||||
|
||||
return true
|
||||
})
|
||||
stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
||||
}
|
||||
|
||||
+30
-17
@@ -213,27 +213,40 @@ func testTCPConn2(conn net.Conn, payloadSize int, timeout time.Duration) func()
|
||||
"\tSys =", units.ByteSize(m.Sys).String(),
|
||||
"\tNumGC =", m.NumGC)
|
||||
}()
|
||||
payload := make([]byte, payloadSize)
|
||||
common.Must2(rand.Read(payload))
|
||||
singleWrite := func(length int) error {
|
||||
payload := make([]byte, length)
|
||||
common.Must2(rand.Read(payload))
|
||||
|
||||
nBytes, err := conn.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nBytes != len(payload) {
|
||||
return errors.New("expect ", len(payload), " written, but actually ", nBytes)
|
||||
}
|
||||
nBytes, err := conn.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nBytes != len(payload) {
|
||||
return errors.New("expect ", len(payload), " written, but actually ", nBytes)
|
||||
}
|
||||
|
||||
response, err := readFrom2(conn, timeout, payloadSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = response
|
||||
response, err := readFrom2(conn, timeout, length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = response
|
||||
|
||||
if r := bytes.Compare(response, xor(payload)); r != 0 {
|
||||
return errors.New(r)
|
||||
}
|
||||
if r := bytes.Compare(response, xor(payload)); r != 0 {
|
||||
return errors.New(r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
for payloadSize > 0 {
|
||||
sizeToWrite := 1024
|
||||
if payloadSize < 1024 {
|
||||
sizeToWrite = payloadSize
|
||||
}
|
||||
if err := singleWrite(sizeToWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
payloadSize -= sizeToWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ func TestSimpleTLSConnection(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -48,7 +50,7 @@ func TestSimpleTLSConnection(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -104,7 +106,7 @@ func TestSimpleTLSConnection(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -247,6 +249,8 @@ func TestTLSOverKCP(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := udp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -260,7 +264,7 @@ func TestTLSOverKCP(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -317,7 +321,7 @@ func TestTLSOverKCP(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -343,6 +347,8 @@ func TestTLSOverWebSocket(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -356,7 +362,7 @@ func TestTLSOverWebSocket(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -419,7 +425,7 @@ func TestTLSOverWebSocket(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -449,6 +455,8 @@ func TestGRPC(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -468,7 +476,7 @@ func TestGRPC(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -531,7 +539,7 @@ func TestGRPC(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -561,6 +569,8 @@ func TestGRPCMultiMode(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -580,7 +590,7 @@ func TestGRPCMultiMode(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -643,7 +653,7 @@ func TestGRPCMultiMode(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -672,7 +682,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
certificateDer := cert.MustGenerate(nil)
|
||||
certificateDer, _ := cert.MustGenerate(nil)
|
||||
certificate := tls.ParseCertificate(certificateDer)
|
||||
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||
userID := protocol.NewID(uuid.New())
|
||||
@@ -743,7 +753,6 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{certHash},
|
||||
}),
|
||||
},
|
||||
@@ -769,7 +778,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
certificateDer := cert.MustGenerate(nil)
|
||||
certificateDer, _ := cert.MustGenerate(nil)
|
||||
certificate := tls.ParseCertificate(certificateDer)
|
||||
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||
certHash[1] += 1
|
||||
@@ -841,7 +850,6 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{certHash},
|
||||
}),
|
||||
},
|
||||
@@ -867,7 +875,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
certificateDer := cert.MustGenerate(nil)
|
||||
certificateDer, _ := cert.MustGenerate(nil)
|
||||
certificate := tls.ParseCertificate(certificateDer)
|
||||
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||
userID := protocol.NewID(uuid.New())
|
||||
@@ -939,7 +947,6 @@ func TestUTLSConnectionPinned(t *testing.T) {
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Fingerprint: "random",
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{certHash},
|
||||
}),
|
||||
},
|
||||
@@ -965,7 +972,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
|
||||
dest, err := tcpServer.Start()
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
certificateDer := cert.MustGenerate(nil)
|
||||
certificateDer, _ := cert.MustGenerate(nil)
|
||||
certificate := tls.ParseCertificate(certificateDer)
|
||||
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||
certHash[1] += 1
|
||||
@@ -1038,7 +1045,6 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Fingerprint: "random",
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{certHash},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestVless(t *testing.T) {
|
||||
Vnext: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
@@ -129,6 +129,8 @@ func TestVlessTls(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -148,7 +150,7 @@ func TestVlessTls(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -198,7 +200,7 @@ func TestVlessTls(t *testing.T) {
|
||||
Vnext: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
}),
|
||||
@@ -217,7 +219,7 @@ func TestVlessTls(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -247,6 +249,8 @@ func TestVlessXtlsVision(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer tcpServer.Close()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
userID := protocol.NewID(uuid.New())
|
||||
serverPort := tcp.PickPort()
|
||||
serverConfig := &core.Config{
|
||||
@@ -266,7 +270,7 @@ func TestVlessXtlsVision(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -317,7 +321,7 @@ func TestVlessXtlsVision(t *testing.T) {
|
||||
Vnext: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
Flow: vless.XRV,
|
||||
@@ -337,7 +341,7 @@ func TestVlessXtlsVision(t *testing.T) {
|
||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
AllowInsecure: true,
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -447,7 +451,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
|
||||
Vnext: &protocol.ServerEndpoint{
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
Port: uint32(serverPort),
|
||||
User: &protocol.User{
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&vless.Account{
|
||||
Id: userID.String(),
|
||||
Flow: vless.XRV,
|
||||
|
||||
@@ -90,7 +90,7 @@ func (c *StreamConfig) GetEffectiveSecuritySettings() (interface{}, error) {
|
||||
}
|
||||
|
||||
func (c *StreamConfig) HasSecuritySettings() bool {
|
||||
return len(c.SecurityType) > 0
|
||||
return len(c.SecuritySettings) > 0
|
||||
}
|
||||
|
||||
func (c *ProxyConfig) HasTag() bool {
|
||||
@@ -130,7 +130,7 @@ func (s DomainStrategy) FallbackIP6() bool {
|
||||
}
|
||||
|
||||
func (s DomainStrategy) GetDynamicStrategy(addrFamily net.AddressFamily) DomainStrategy {
|
||||
if addrFamily.IsDomain(){
|
||||
if addrFamily.IsDomain() {
|
||||
return s
|
||||
}
|
||||
switch s {
|
||||
|
||||
+158
-224
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v5.28.2
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.1
|
||||
// source: transport/internet/config.proto
|
||||
|
||||
package internet
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -209,14 +210,13 @@ func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
type TransportConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Transport protocol name.
|
||||
ProtocolName string `protobuf:"bytes,3,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"`
|
||||
// Specific transport protocol settings.
|
||||
Settings *serial.TypedMessage `protobuf:"bytes,2,opt,name=settings,proto3" json:"settings,omitempty"`
|
||||
Settings *serial.TypedMessage `protobuf:"bytes,2,opt,name=settings,proto3" json:"settings,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TransportConfig) Reset() {
|
||||
@@ -264,12 +264,9 @@ func (x *TransportConfig) GetSettings() *serial.TypedMessage {
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Address *net.IPOrDomain `protobuf:"bytes,8,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Port uint32 `protobuf:"varint,9,opt,name=port,proto3" json:"port,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Address *net.IPOrDomain `protobuf:"bytes,8,opt,name=address,proto3" json:"address,omitempty"`
|
||||
Port uint32 `protobuf:"varint,9,opt,name=port,proto3" json:"port,omitempty"`
|
||||
// Effective network.
|
||||
ProtocolName string `protobuf:"bytes,5,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"`
|
||||
TransportSettings []*TransportConfig `protobuf:"bytes,2,rep,name=transport_settings,json=transportSettings,proto3" json:"transport_settings,omitempty"`
|
||||
@@ -277,7 +274,11 @@ type StreamConfig struct {
|
||||
SecurityType string `protobuf:"bytes,3,opt,name=security_type,json=securityType,proto3" json:"security_type,omitempty"`
|
||||
// Transport security settings. They can be either TLS or REALITY.
|
||||
SecuritySettings []*serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"`
|
||||
Udpmasks []*serial.TypedMessage `protobuf:"bytes,10,rep,name=udpmasks,proto3" json:"udpmasks,omitempty"`
|
||||
Tcpmasks []*serial.TypedMessage `protobuf:"bytes,11,rep,name=tcpmasks,proto3" json:"tcpmasks,omitempty"`
|
||||
SocketSettings *SocketConfig `protobuf:"bytes,6,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StreamConfig) Reset() {
|
||||
@@ -352,6 +353,20 @@ func (x *StreamConfig) GetSecuritySettings() []*serial.TypedMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StreamConfig) GetUdpmasks() []*serial.TypedMessage {
|
||||
if x != nil {
|
||||
return x.Udpmasks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StreamConfig) GetTcpmasks() []*serial.TypedMessage {
|
||||
if x != nil {
|
||||
return x.Tcpmasks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StreamConfig) GetSocketSettings() *SocketConfig {
|
||||
if x != nil {
|
||||
return x.SocketSettings
|
||||
@@ -360,12 +375,11 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
|
||||
}
|
||||
|
||||
type ProxyConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProxyConfig) Reset() {
|
||||
@@ -413,16 +427,15 @@ func (x *ProxyConfig) GetTransportLayerProxy() bool {
|
||||
}
|
||||
|
||||
type CustomSockopt struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
|
||||
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
|
||||
Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Opt string `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"`
|
||||
Value string `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
|
||||
Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
|
||||
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
|
||||
Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Opt string `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"`
|
||||
Value string `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
|
||||
Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) Reset() {
|
||||
@@ -499,10 +512,7 @@ func (x *CustomSockopt) GetType() string {
|
||||
|
||||
// SocketConfig is options to be applied on network sockets.
|
||||
type SocketConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Mark of the connection. If non-zero, the value will be set to SO_MARK.
|
||||
Mark int32 `protobuf:"varint,1,opt,name=mark,proto3" json:"mark,omitempty"`
|
||||
// TFO is the state of TFO settings.
|
||||
@@ -531,6 +541,8 @@ type SocketConfig struct {
|
||||
AddressPortStrategy AddressPortStrategy `protobuf:"varint,21,opt,name=address_port_strategy,json=addressPortStrategy,proto3,enum=xray.transport.internet.AddressPortStrategy" json:"address_port_strategy,omitempty"`
|
||||
HappyEyeballs *HappyEyeballsConfig `protobuf:"bytes,22,opt,name=happy_eyeballs,json=happyEyeballs,proto3" json:"happy_eyeballs,omitempty"`
|
||||
TrustedXForwardedFor []string `protobuf:"bytes,23,rep,name=trusted_x_forwarded_for,json=trustedXForwardedFor,proto3" json:"trusted_x_forwarded_for,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SocketConfig) Reset() {
|
||||
@@ -725,14 +737,13 @@ func (x *SocketConfig) GetTrustedXForwardedFor() []string {
|
||||
}
|
||||
|
||||
type HappyEyeballsConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
PrioritizeIpv6 bool `protobuf:"varint,1,opt,name=prioritize_ipv6,json=prioritizeIpv6,proto3" json:"prioritize_ipv6,omitempty"`
|
||||
Interleave uint32 `protobuf:"varint,2,opt,name=interleave,proto3" json:"interleave,omitempty"`
|
||||
TryDelayMs uint64 `protobuf:"varint,3,opt,name=try_delayMs,json=tryDelayMs,proto3" json:"try_delayMs,omitempty"`
|
||||
MaxConcurrentTry uint32 `protobuf:"varint,4,opt,name=max_concurrent_try,json=maxConcurrentTry,proto3" json:"max_concurrent_try,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PrioritizeIpv6 bool `protobuf:"varint,1,opt,name=prioritize_ipv6,json=prioritizeIpv6,proto3" json:"prioritize_ipv6,omitempty"`
|
||||
Interleave uint32 `protobuf:"varint,2,opt,name=interleave,proto3" json:"interleave,omitempty"`
|
||||
TryDelayMs uint64 `protobuf:"varint,3,opt,name=try_delayMs,json=tryDelayMs,proto3" json:"try_delayMs,omitempty"`
|
||||
MaxConcurrentTry uint32 `protobuf:"varint,4,opt,name=max_concurrent_try,json=maxConcurrentTry,proto3" json:"max_concurrent_try,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HappyEyeballsConfig) Reset() {
|
||||
@@ -795,184 +806,106 @@ func (x *HappyEyeballsConfig) GetMaxConcurrentTry() uint32 {
|
||||
|
||||
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_transport_internet_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x17, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x3c, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x9b, 0x03,
|
||||
0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35,
|
||||
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
|
||||
0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57,
|
||||
0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74,
|
||||
0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53,
|
||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x63, 0x75, 0x72,
|
||||
0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4d, 0x0a, 0x11,
|
||||
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70,
|
||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72,
|
||||
0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4e, 0x0a, 0x0f, 0x73,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x6f, 0x63,
|
||||
0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x51, 0x0a, 0x0b, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61,
|
||||
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x13,
|
||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x93,
|
||||
0x01, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77,
|
||||
0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
|
||||
0x72, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x74, 0x79, 0x70, 0x65, 0x22, 0x89, 0x09, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74,
|
||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74,
|
||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
|
||||
0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65,
|
||||
0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73,
|
||||
0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64,
|
||||
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b,
|
||||
0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62,
|
||||
0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
|
||||
0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65,
|
||||
0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||
0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f,
|
||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
|
||||
0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
|
||||
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e,
|
||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78,
|
||||
0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c,
|
||||
0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
|
||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f,
|
||||
0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18,
|
||||
0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,
|
||||
0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63,
|
||||
0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0d, 0x74, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36,
|
||||
0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64,
|
||||
0x6f, 0x77, 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
|
||||
0x74, 0x63, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28,
|
||||
0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
|
||||
0x75, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65,
|
||||
0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f,
|
||||
0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74,
|
||||
0x63, 0x70, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x65,
|
||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x6e,
|
||||
0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x70,
|
||||
0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x4d, 0x70,
|
||||
0x74, 0x63, 0x70, 0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63,
|
||||
0x6b, 0x6f, 0x70, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f,
|
||||
0x70, 0x74, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70,
|
||||
0x74, 0x12, 0x60, 0x0a, 0x15, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x13,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||
0x65, 0x67, 0x79, 0x12, 0x53, 0x0a, 0x0e, 0x68, 0x61, 0x70, 0x70, 0x79, 0x5f, 0x65, 0x79, 0x65,
|
||||
0x62, 0x61, 0x6c, 0x6c, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x61, 0x70, 0x70, 0x79, 0x45, 0x79, 0x65, 0x62, 0x61,
|
||||
0x6c, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x68, 0x61, 0x70, 0x70, 0x79,
|
||||
0x45, 0x79, 0x65, 0x62, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x72, 0x75, 0x73,
|
||||
0x74, 0x65, 0x64, 0x5f, 0x78, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x5f,
|
||||
0x66, 0x6f, 0x72, 0x18, 0x17, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x74, 0x72, 0x75, 0x73, 0x74,
|
||||
0x65, 0x64, 0x58, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x22,
|
||||
0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a,
|
||||
0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02,
|
||||
0x22, 0xad, 0x01, 0x0a, 0x13, 0x48, 0x61, 0x70, 0x70, 0x79, 0x45, 0x79, 0x65, 0x62, 0x61, 0x6c,
|
||||
0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x5f, 0x69, 0x70, 0x76, 0x36, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x49, 0x70, 0x76,
|
||||
0x36, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x65, 0x61, 0x76,
|
||||
0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x61, 0x79,
|
||||
0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72,
|
||||
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10,
|
||||
0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x79,
|
||||
0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
|
||||
0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a,
|
||||
0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53,
|
||||
0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49,
|
||||
0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36,
|
||||
0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x05,
|
||||
0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06, 0x12, 0x0d,
|
||||
0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12, 0x0d, 0x0a,
|
||||
0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a,
|
||||
0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a,
|
||||
0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x2a, 0x97, 0x01, 0x0a,
|
||||
0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61,
|
||||
0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0f,
|
||||
0x0a, 0x0b, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x01, 0x12,
|
||||
0x12, 0x0a, 0x0e, 0x53, 0x72, 0x76, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c,
|
||||
0x79, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e,
|
||||
0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x78,
|
||||
0x74, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x54,
|
||||
0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x05, 0x12,
|
||||
0x15, 0x0a, 0x11, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x10, 0x06, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f,
|
||||
0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61,
|
||||
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
const file_transport_internet_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1ftransport/internet/config.proto\x12\x17xray.transport.internet\x1a!common/serial/typed_message.proto\x1a\x18common/net/address.proto\"t\n" +
|
||||
"\x0fTransportConfig\x12#\n" +
|
||||
"\rprotocol_name\x18\x03 \x01(\tR\fprotocolName\x12<\n" +
|
||||
"\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\x97\x04\n" +
|
||||
"\fStreamConfig\x125\n" +
|
||||
"\aaddress\x18\b \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
|
||||
"\x04port\x18\t \x01(\rR\x04port\x12#\n" +
|
||||
"\rprotocol_name\x18\x05 \x01(\tR\fprotocolName\x12W\n" +
|
||||
"\x12transport_settings\x18\x02 \x03(\v2(.xray.transport.internet.TransportConfigR\x11transportSettings\x12#\n" +
|
||||
"\rsecurity_type\x18\x03 \x01(\tR\fsecurityType\x12M\n" +
|
||||
"\x11security_settings\x18\x04 \x03(\v2 .xray.common.serial.TypedMessageR\x10securitySettings\x12<\n" +
|
||||
"\budpmasks\x18\n" +
|
||||
" \x03(\v2 .xray.common.serial.TypedMessageR\budpmasks\x12<\n" +
|
||||
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12N\n" +
|
||||
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"Q\n" +
|
||||
"\vProxyConfig\x12\x10\n" +
|
||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
|
||||
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
|
||||
"\rCustomSockopt\x12\x16\n" +
|
||||
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
|
||||
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
|
||||
"\x05level\x18\x03 \x01(\tR\x05level\x12\x10\n" +
|
||||
"\x03opt\x18\x04 \x01(\tR\x03opt\x12\x14\n" +
|
||||
"\x05value\x18\x05 \x01(\tR\x05value\x12\x12\n" +
|
||||
"\x04type\x18\x06 \x01(\tR\x04type\"\x89\t\n" +
|
||||
"\fSocketConfig\x12\x12\n" +
|
||||
"\x04mark\x18\x01 \x01(\x05R\x04mark\x12\x10\n" +
|
||||
"\x03tfo\x18\x02 \x01(\x05R\x03tfo\x12H\n" +
|
||||
"\x06tproxy\x18\x03 \x01(\x0e20.xray.transport.internet.SocketConfig.TProxyModeR\x06tproxy\x12A\n" +
|
||||
"\x1dreceive_original_dest_address\x18\x04 \x01(\bR\x1areceiveOriginalDestAddress\x12!\n" +
|
||||
"\fbind_address\x18\x05 \x01(\fR\vbindAddress\x12\x1b\n" +
|
||||
"\tbind_port\x18\x06 \x01(\rR\bbindPort\x122\n" +
|
||||
"\x15accept_proxy_protocol\x18\a \x01(\bR\x13acceptProxyProtocol\x12P\n" +
|
||||
"\x0fdomain_strategy\x18\b \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12!\n" +
|
||||
"\fdialer_proxy\x18\t \x01(\tR\vdialerProxy\x125\n" +
|
||||
"\x17tcp_keep_alive_interval\x18\n" +
|
||||
" \x01(\x05R\x14tcpKeepAliveInterval\x12-\n" +
|
||||
"\x13tcp_keep_alive_idle\x18\v \x01(\x05R\x10tcpKeepAliveIdle\x12%\n" +
|
||||
"\x0etcp_congestion\x18\f \x01(\tR\rtcpCongestion\x12\x1c\n" +
|
||||
"\tinterface\x18\r \x01(\tR\tinterface\x12\x16\n" +
|
||||
"\x06v6only\x18\x0e \x01(\bR\x06v6only\x12(\n" +
|
||||
"\x10tcp_window_clamp\x18\x0f \x01(\x05R\x0etcpWindowClamp\x12(\n" +
|
||||
"\x10tcp_user_timeout\x18\x10 \x01(\x05R\x0etcpUserTimeout\x12\x1e\n" +
|
||||
"\vtcp_max_seg\x18\x11 \x01(\x05R\ttcpMaxSeg\x12\x1c\n" +
|
||||
"\tpenetrate\x18\x12 \x01(\bR\tpenetrate\x12\x1b\n" +
|
||||
"\ttcp_mptcp\x18\x13 \x01(\bR\btcpMptcp\x12L\n" +
|
||||
"\rcustomSockopt\x18\x14 \x03(\v2&.xray.transport.internet.CustomSockoptR\rcustomSockopt\x12`\n" +
|
||||
"\x15address_port_strategy\x18\x15 \x01(\x0e2,.xray.transport.internet.AddressPortStrategyR\x13addressPortStrategy\x12S\n" +
|
||||
"\x0ehappy_eyeballs\x18\x16 \x01(\v2,.xray.transport.internet.HappyEyeballsConfigR\rhappyEyeballs\x125\n" +
|
||||
"\x17trusted_x_forwarded_for\x18\x17 \x03(\tR\x14trustedXForwardedFor\"/\n" +
|
||||
"\n" +
|
||||
"TProxyMode\x12\a\n" +
|
||||
"\x03Off\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06TProxy\x10\x01\x12\f\n" +
|
||||
"\bRedirect\x10\x02\"\xad\x01\n" +
|
||||
"\x13HappyEyeballsConfig\x12'\n" +
|
||||
"\x0fprioritize_ipv6\x18\x01 \x01(\bR\x0eprioritizeIpv6\x12\x1e\n" +
|
||||
"\n" +
|
||||
"interleave\x18\x02 \x01(\rR\n" +
|
||||
"interleave\x12\x1f\n" +
|
||||
"\vtry_delayMs\x18\x03 \x01(\x04R\n" +
|
||||
"tryDelayMs\x12,\n" +
|
||||
"\x12max_concurrent_try\x18\x04 \x01(\rR\x10maxConcurrentTry*\xa9\x01\n" +
|
||||
"\x0eDomainStrategy\x12\t\n" +
|
||||
"\x05AS_IS\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06USE_IP\x10\x01\x12\v\n" +
|
||||
"\aUSE_IP4\x10\x02\x12\v\n" +
|
||||
"\aUSE_IP6\x10\x03\x12\f\n" +
|
||||
"\bUSE_IP46\x10\x04\x12\f\n" +
|
||||
"\bUSE_IP64\x10\x05\x12\f\n" +
|
||||
"\bFORCE_IP\x10\x06\x12\r\n" +
|
||||
"\tFORCE_IP4\x10\a\x12\r\n" +
|
||||
"\tFORCE_IP6\x10\b\x12\x0e\n" +
|
||||
"\n" +
|
||||
"FORCE_IP46\x10\t\x12\x0e\n" +
|
||||
"\n" +
|
||||
"FORCE_IP64\x10\n" +
|
||||
"*\x97\x01\n" +
|
||||
"\x13AddressPortStrategy\x12\b\n" +
|
||||
"\x04None\x10\x00\x12\x0f\n" +
|
||||
"\vSrvPortOnly\x10\x01\x12\x12\n" +
|
||||
"\x0eSrvAddressOnly\x10\x02\x12\x15\n" +
|
||||
"\x11SrvPortAndAddress\x10\x03\x12\x0f\n" +
|
||||
"\vTxtPortOnly\x10\x04\x12\x12\n" +
|
||||
"\x0eTxtAddressOnly\x10\x05\x12\x15\n" +
|
||||
"\x11TxtPortAndAddress\x10\x06Bg\n" +
|
||||
"\x1bcom.xray.transport.internetP\x01Z,github.com/xtls/xray-core/transport/internet\xaa\x02\x17Xray.Transport.Internetb\x06proto3"
|
||||
|
||||
var (
|
||||
file_transport_internet_config_proto_rawDescOnce sync.Once
|
||||
file_transport_internet_config_proto_rawDescData = file_transport_internet_config_proto_rawDesc
|
||||
file_transport_internet_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
||||
file_transport_internet_config_proto_rawDescOnce.Do(func() {
|
||||
file_transport_internet_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_config_proto_rawDescData)
|
||||
file_transport_internet_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)))
|
||||
})
|
||||
return file_transport_internet_config_proto_rawDescData
|
||||
}
|
||||
@@ -997,17 +930,19 @@ var file_transport_internet_config_proto_depIdxs = []int32{
|
||||
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
||||
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
||||
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||
7, // 4: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||
2, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
6, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||
1, // 8: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
||||
8, // 9: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
||||
10, // [10:10] is the sub-list for method output_type
|
||||
10, // [10:10] is the sub-list for method input_type
|
||||
10, // [10:10] is the sub-list for extension type_name
|
||||
10, // [10:10] is the sub-list for extension extendee
|
||||
0, // [0:10] is the sub-list for field type_name
|
||||
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
|
||||
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
|
||||
7, // 6: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||
2, // 7: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||
0, // 8: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
6, // 9: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||
1, // 10: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
||||
8, // 11: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_config_proto_init() }
|
||||
@@ -1019,7 +954,7 @@ func file_transport_internet_config_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
|
||||
NumEnums: 3,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
@@ -1031,7 +966,6 @@ func file_transport_internet_config_proto_init() {
|
||||
MessageInfos: file_transport_internet_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_transport_internet_config_proto = out.File
|
||||
file_transport_internet_config_proto_rawDesc = nil
|
||||
file_transport_internet_config_proto_goTypes = nil
|
||||
file_transport_internet_config_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ message StreamConfig {
|
||||
// Transport security settings. They can be either TLS or REALITY.
|
||||
repeated xray.common.serial.TypedMessage security_settings = 4;
|
||||
|
||||
repeated xray.common.serial.TypedMessage udpmasks = 10;
|
||||
repeated xray.common.serial.TypedMessage tcpmasks = 11;
|
||||
|
||||
SocketConfig socket_settings = 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package finalmask
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Udpmask interface {
|
||||
UDP()
|
||||
|
||||
WrapConnClient(net.Conn) (net.Conn, error)
|
||||
WrapConnServer(net.Conn) (net.Conn, error)
|
||||
|
||||
WrapPacketConnClient(net.PacketConn) (net.PacketConn, error)
|
||||
WrapPacketConnServer(net.PacketConn) (net.PacketConn, error)
|
||||
|
||||
Size() int
|
||||
Serialize([]byte)
|
||||
}
|
||||
|
||||
type UdpmaskManager struct {
|
||||
udpmasks []Udpmask
|
||||
}
|
||||
|
||||
func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
|
||||
return &UdpmaskManager{
|
||||
udpmasks: udpmasks,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range m.udpmasks {
|
||||
raw, err = mask.WrapConnClient(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range m.udpmasks {
|
||||
raw, err = mask.WrapConnServer(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
|
||||
var err error
|
||||
for _, mask := range m.udpmasks {
|
||||
raw, err = mask.WrapPacketConnClient(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
|
||||
var err error
|
||||
for _, mask := range m.udpmasks {
|
||||
raw, err = mask.WrapPacketConnServer(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) Size() int {
|
||||
size := 0
|
||||
for _, mask := range m.udpmasks {
|
||||
size += mask.Size()
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (m *UdpmaskManager) Serialize(b []byte) {
|
||||
index := 0
|
||||
for _, mask := range m.udpmasks {
|
||||
mask.Serialize(b[index:])
|
||||
index += mask.Size()
|
||||
}
|
||||
}
|
||||
|
||||
type Tcpmask interface {
|
||||
TCP()
|
||||
|
||||
WrapConnClient(net.Conn) (net.Conn, error)
|
||||
WrapConnServer(net.Conn) (net.Conn, error)
|
||||
}
|
||||
|
||||
type TcpmaskManager struct {
|
||||
tcpmasks []Tcpmask
|
||||
}
|
||||
|
||||
func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
|
||||
return &TcpmaskManager{
|
||||
tcpmasks: tcpmasks,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range m.tcpmasks {
|
||||
raw, err = mask.WrapConnClient(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
for _, mask := range m.tcpmasks {
|
||||
raw, err = mask.WrapConnServer(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package salamander
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander/obfs"
|
||||
)
|
||||
|
||||
func (c *Config) UDP() {
|
||||
}
|
||||
|
||||
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (c *Config) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
|
||||
ob, err := obfs.NewSalamanderObfuscator([]byte(c.Password))
|
||||
if err != nil {
|
||||
return nil, errors.New("salamander err").Base(err)
|
||||
}
|
||||
return obfs.WrapPacketConn(raw, ob), nil
|
||||
}
|
||||
|
||||
func (c *Config) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
|
||||
ob, err := obfs.NewSalamanderObfuscator([]byte(c.Password))
|
||||
if err != nil {
|
||||
return nil, errors.New("salamander err").Base(err)
|
||||
}
|
||||
return obfs.WrapPacketConn(raw, ob), nil
|
||||
}
|
||||
|
||||
func (c *Config) Size() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Config) Serialize([]byte) {
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.1
|
||||
// source: transport/internet/udpmask/salamander/config.proto
|
||||
|
||||
package salamander
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_transport_internet_udpmask_salamander_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_udpmask_salamander_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_udpmask_salamander_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetPassword() string {
|
||||
if x != nil {
|
||||
return x.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_transport_internet_udpmask_salamander_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_udpmask_salamander_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"2transport/internet/udpmask/salamander/config.proto\x12*xray.transport.internet.udpmask.salamander\"$\n" +
|
||||
"\x06Config\x12\x1a\n" +
|
||||
"\bpassword\x18\x01 \x01(\tR\bpasswordB\xa0\x01\n" +
|
||||
".com.xray.transport.internet.udpmask.salamanderP\x01Z?github.com/xtls/xray-core/transport/internet/udpmask/salamander\xaa\x02*Xray.Transport.Internet.Udpmask.Salamanderb\x06proto3"
|
||||
|
||||
var (
|
||||
file_transport_internet_udpmask_salamander_config_proto_rawDescOnce sync.Once
|
||||
file_transport_internet_udpmask_salamander_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_transport_internet_udpmask_salamander_config_proto_rawDescGZIP() []byte {
|
||||
file_transport_internet_udpmask_salamander_config_proto_rawDescOnce.Do(func() {
|
||||
file_transport_internet_udpmask_salamander_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_udpmask_salamander_config_proto_rawDesc), len(file_transport_internet_udpmask_salamander_config_proto_rawDesc)))
|
||||
})
|
||||
return file_transport_internet_udpmask_salamander_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_udpmask_salamander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_transport_internet_udpmask_salamander_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.transport.internet.udpmask.salamander.Config
|
||||
}
|
||||
var file_transport_internet_udpmask_salamander_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_udpmask_salamander_config_proto_init() }
|
||||
func file_transport_internet_udpmask_salamander_config_proto_init() {
|
||||
if File_transport_internet_udpmask_salamander_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_udpmask_salamander_config_proto_rawDesc), len(file_transport_internet_udpmask_salamander_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_transport_internet_udpmask_salamander_config_proto_goTypes,
|
||||
DependencyIndexes: file_transport_internet_udpmask_salamander_config_proto_depIdxs,
|
||||
MessageInfos: file_transport_internet_udpmask_salamander_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_transport_internet_udpmask_salamander_config_proto = out.File
|
||||
file_transport_internet_udpmask_salamander_config_proto_goTypes = nil
|
||||
file_transport_internet_udpmask_salamander_config_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.transport.internet.udpmask.salamander;
|
||||
option csharp_namespace = "Xray.Transport.Internet.Udpmask.Salamander";
|
||||
option go_package = "github.com/xtls/xray-core/transport/internet/udpmask/salamander";
|
||||
option java_package = "com.xray.transport.internet.udpmask.salamander";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Config {
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const udpBufferSize = 2048 // QUIC packets are at most 1500 bytes long, so 2k should be more than enough
|
||||
|
||||
// Obfuscator is the interface that wraps the Obfuscate and Deobfuscate methods.
|
||||
// Both methods return the number of bytes written to out.
|
||||
// If a packet is not valid, the methods should return 0.
|
||||
type Obfuscator interface {
|
||||
Obfuscate(in, out []byte) int
|
||||
Deobfuscate(in, out []byte) int
|
||||
}
|
||||
|
||||
var _ net.PacketConn = (*obfsPacketConn)(nil)
|
||||
|
||||
type obfsPacketConn struct {
|
||||
Conn net.PacketConn
|
||||
Obfs Obfuscator
|
||||
|
||||
readBuf []byte
|
||||
readMutex sync.Mutex
|
||||
writeBuf []byte
|
||||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
// obfsPacketConnUDP is a special case of obfsPacketConn that uses a UDPConn
|
||||
// as the underlying connection. We pass additional methods to quic-go to
|
||||
// enable UDP-specific optimizations.
|
||||
type obfsPacketConnUDP struct {
|
||||
*obfsPacketConn
|
||||
UDPConn *net.UDPConn
|
||||
}
|
||||
|
||||
// WrapPacketConn enables obfuscation on a net.PacketConn.
|
||||
// The obfuscation is transparent to the caller - the n bytes returned by
|
||||
// ReadFrom and WriteTo are the number of original bytes, not after
|
||||
// obfuscation/deobfuscation.
|
||||
func WrapPacketConn(conn net.PacketConn, obfs Obfuscator) net.PacketConn {
|
||||
opc := &obfsPacketConn{
|
||||
Conn: conn,
|
||||
Obfs: obfs,
|
||||
readBuf: make([]byte, udpBufferSize),
|
||||
writeBuf: make([]byte, udpBufferSize),
|
||||
}
|
||||
if udpConn, ok := conn.(*net.UDPConn); ok {
|
||||
return &obfsPacketConnUDP{
|
||||
obfsPacketConn: opc,
|
||||
UDPConn: udpConn,
|
||||
}
|
||||
} else {
|
||||
return opc
|
||||
}
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
for {
|
||||
c.readMutex.Lock()
|
||||
n, addr, err = c.Conn.ReadFrom(c.readBuf)
|
||||
if n <= 0 {
|
||||
c.readMutex.Unlock()
|
||||
return n, addr, err
|
||||
}
|
||||
n = c.Obfs.Deobfuscate(c.readBuf[:n], p)
|
||||
c.readMutex.Unlock()
|
||||
if n > 0 || err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
// Invalid packet, try again
|
||||
}
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.writeMutex.Lock()
|
||||
nn := c.Obfs.Obfuscate(p, c.writeBuf)
|
||||
_, err = c.Conn.WriteTo(c.writeBuf[:nn], addr)
|
||||
c.writeMutex.Unlock()
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) Close() error {
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) LocalAddr() net.Addr {
|
||||
return c.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) SetDeadline(t time.Time) error {
|
||||
return c.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *obfsPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// UDP-specific methods below
|
||||
|
||||
func (c *obfsPacketConnUDP) SetReadBuffer(bytes int) error {
|
||||
return c.UDPConn.SetReadBuffer(bytes)
|
||||
}
|
||||
|
||||
func (c *obfsPacketConnUDP) SetWriteBuffer(bytes int) error {
|
||||
return c.UDPConn.SetWriteBuffer(bytes)
|
||||
}
|
||||
|
||||
func (c *obfsPacketConnUDP) SyscallConn() (syscall.RawConn, error) {
|
||||
return c.UDPConn.SyscallConn()
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
smPSKMinLen = 4
|
||||
smSaltLen = 8
|
||||
smKeyLen = blake2b.Size256
|
||||
)
|
||||
|
||||
var _ Obfuscator = (*SalamanderObfuscator)(nil)
|
||||
|
||||
var ErrPSKTooShort = fmt.Errorf("PSK must be at least %d bytes", smPSKMinLen)
|
||||
|
||||
// SalamanderObfuscator is an obfuscator that obfuscates each packet with
|
||||
// the BLAKE2b-256 hash of a pre-shared key combined with a random salt.
|
||||
// Packet format: [8-byte salt][payload]
|
||||
type SalamanderObfuscator struct {
|
||||
PSK []byte
|
||||
RandSrc *rand.Rand
|
||||
|
||||
lk sync.Mutex
|
||||
}
|
||||
|
||||
func NewSalamanderObfuscator(psk []byte) (*SalamanderObfuscator, error) {
|
||||
if len(psk) < smPSKMinLen {
|
||||
return nil, ErrPSKTooShort
|
||||
}
|
||||
return &SalamanderObfuscator{
|
||||
PSK: psk,
|
||||
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *SalamanderObfuscator) Obfuscate(in, out []byte) int {
|
||||
outLen := len(in) + smSaltLen
|
||||
if len(out) < outLen {
|
||||
return 0
|
||||
}
|
||||
o.lk.Lock()
|
||||
_, _ = o.RandSrc.Read(out[:smSaltLen])
|
||||
o.lk.Unlock()
|
||||
key := o.key(out[:smSaltLen])
|
||||
for i, c := range in {
|
||||
out[i+smSaltLen] = c ^ key[i%smKeyLen]
|
||||
}
|
||||
return outLen
|
||||
}
|
||||
|
||||
func (o *SalamanderObfuscator) Deobfuscate(in, out []byte) int {
|
||||
outLen := len(in) - smSaltLen
|
||||
if outLen <= 0 || len(out) < outLen {
|
||||
return 0
|
||||
}
|
||||
key := o.key(in[:smSaltLen])
|
||||
for i, c := range in[smSaltLen:] {
|
||||
out[i] = c ^ key[i%smKeyLen]
|
||||
}
|
||||
return outLen
|
||||
}
|
||||
|
||||
func (o *SalamanderObfuscator) key(salt []byte) [smKeyLen]byte {
|
||||
return blake2b.Sum256(append(o.PSK, salt...))
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkSalamanderObfuscator_Obfuscate(b *testing.B) {
|
||||
o, _ := NewSalamanderObfuscator([]byte("average_password"))
|
||||
in := make([]byte, 1200)
|
||||
_, _ = rand.Read(in)
|
||||
out := make([]byte, 2048)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
o.Obfuscate(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSalamanderObfuscator_Deobfuscate(b *testing.B) {
|
||||
o, _ := NewSalamanderObfuscator([]byte("average_password"))
|
||||
in := make([]byte, 1200)
|
||||
_, _ = rand.Read(in)
|
||||
out := make([]byte, 2048)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
o.Deobfuscate(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSalamanderObfuscator(t *testing.T) {
|
||||
o, _ := NewSalamanderObfuscator([]byte("average_password"))
|
||||
in := make([]byte, 1200)
|
||||
oOut := make([]byte, 2048)
|
||||
dOut := make([]byte, 2048)
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, _ = rand.Read(in)
|
||||
n := o.Obfuscate(in, oOut)
|
||||
assert.Equal(t, len(in)+smSaltLen, n)
|
||||
n = o.Deobfuscate(oOut[:n], dOut)
|
||||
assert.Equal(t, len(in), n)
|
||||
assert.Equal(t, in, dOut[:n])
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,8 @@ func Test_listenHTTPUpgradeAndDial_TLS(t *testing.T) {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "httpupgrade",
|
||||
ProtocolSettings: &Config{
|
||||
@@ -189,8 +191,8 @@ func Test_listenHTTPUpgradeAndDial_TLS(t *testing.T) {
|
||||
},
|
||||
SecurityType: "tls",
|
||||
SecuritySettings: &tls.Config{
|
||||
AllowInsecure: true,
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("localhost")))},
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(ct)},
|
||||
PinnedPeerCertSha256: [][]byte{ctHash[:]},
|
||||
},
|
||||
}
|
||||
listen, err := ListenHTTPUpgrade(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
|
||||
)
|
||||
|
||||
const (
|
||||
closeErrCodeOK = 0x100 // HTTP3 ErrCodeNoError
|
||||
closeErrCodeProtocolError = 0x101 // HTTP3 ErrCodeGeneralProtocolError
|
||||
|
||||
MaxDatagramFrameSize = 1200
|
||||
|
||||
URLHost = "hysteria"
|
||||
URLPath = "/auth"
|
||||
|
||||
RequestHeaderAuth = "Hysteria-Auth"
|
||||
ResponseHeaderUDPEnabled = "Hysteria-UDP"
|
||||
CommonHeaderCCRX = "Hysteria-CC-RX"
|
||||
CommonHeaderPadding = "Hysteria-Padding"
|
||||
|
||||
StatusAuthOK = 233
|
||||
|
||||
udpMessageChanSize = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
|
||||
// authResponsePadding = padding.Padding{Min: 256, Max: 2048}
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusUnknown Status = iota
|
||||
StatusActive
|
||||
StatusInactive
|
||||
)
|
||||
|
||||
const protocolName = "hysteria"
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
|
||||
return new(Config)
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.33.1
|
||||
// source: transport/internet/hysteria/config.proto
|
||||
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Auth string `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"`
|
||||
Congestion string `protobuf:"bytes,3,opt,name=congestion,proto3" json:"congestion,omitempty"`
|
||||
Up uint64 `protobuf:"varint,4,opt,name=up,proto3" json:"up,omitempty"`
|
||||
Down uint64 `protobuf:"varint,5,opt,name=down,proto3" json:"down,omitempty"`
|
||||
Ports []uint32 `protobuf:"varint,6,rep,packed,name=ports,proto3" json:"ports,omitempty"`
|
||||
IntervalMin int64 `protobuf:"varint,7,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
|
||||
IntervalMax int64 `protobuf:"varint,8,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
|
||||
InitStreamReceiveWindow uint64 `protobuf:"varint,9,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
|
||||
MaxStreamReceiveWindow uint64 `protobuf:"varint,10,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
|
||||
InitConnReceiveWindow uint64 `protobuf:"varint,11,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
|
||||
MaxConnReceiveWindow uint64 `protobuf:"varint,12,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
|
||||
MaxIdleTimeout int64 `protobuf:"varint,13,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
||||
KeepAlivePeriod int64 `protobuf:"varint,14,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
||||
DisablePathMtuDiscovery bool `protobuf:"varint,15,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_transport_internet_hysteria_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_hysteria_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetAuth() string {
|
||||
if x != nil {
|
||||
return x.Auth
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetCongestion() string {
|
||||
if x != nil {
|
||||
return x.Congestion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetUp() uint64 {
|
||||
if x != nil {
|
||||
return x.Up
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetDown() uint64 {
|
||||
if x != nil {
|
||||
return x.Down
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetPorts() []uint32 {
|
||||
if x != nil {
|
||||
return x.Ports
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetIntervalMin() int64 {
|
||||
if x != nil {
|
||||
return x.IntervalMin
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetIntervalMax() int64 {
|
||||
if x != nil {
|
||||
return x.IntervalMax
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetInitStreamReceiveWindow() uint64 {
|
||||
if x != nil {
|
||||
return x.InitStreamReceiveWindow
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxStreamReceiveWindow() uint64 {
|
||||
if x != nil {
|
||||
return x.MaxStreamReceiveWindow
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetInitConnReceiveWindow() uint64 {
|
||||
if x != nil {
|
||||
return x.InitConnReceiveWindow
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxConnReceiveWindow() uint64 {
|
||||
if x != nil {
|
||||
return x.MaxConnReceiveWindow
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxIdleTimeout() int64 {
|
||||
if x != nil {
|
||||
return x.MaxIdleTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetKeepAlivePeriod() int64 {
|
||||
if x != nil {
|
||||
return x.KeepAlivePeriod
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetDisablePathMtuDiscovery() bool {
|
||||
if x != nil {
|
||||
return x.DisablePathMtuDiscovery
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xd1\x04\n" +
|
||||
"\x06Config\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
|
||||
"\x04auth\x18\x02 \x01(\tR\x04auth\x12\x1e\n" +
|
||||
"\n" +
|
||||
"congestion\x18\x03 \x01(\tR\n" +
|
||||
"congestion\x12\x0e\n" +
|
||||
"\x02up\x18\x04 \x01(\x04R\x02up\x12\x12\n" +
|
||||
"\x04down\x18\x05 \x01(\x04R\x04down\x12\x14\n" +
|
||||
"\x05ports\x18\x06 \x03(\rR\x05ports\x12!\n" +
|
||||
"\finterval_min\x18\a \x01(\x03R\vintervalMin\x12!\n" +
|
||||
"\finterval_max\x18\b \x01(\x03R\vintervalMax\x12;\n" +
|
||||
"\x1ainit_stream_receive_window\x18\t \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
|
||||
"\x19max_stream_receive_window\x18\n" +
|
||||
" \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
|
||||
"\x18init_conn_receive_window\x18\v \x01(\x04R\x15initConnReceiveWindow\x125\n" +
|
||||
"\x17max_conn_receive_window\x18\f \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
|
||||
"\x10max_idle_timeout\x18\r \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
||||
"\x11keep_alive_period\x18\x0e \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
||||
"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscoveryB\x82\x01\n" +
|
||||
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
file_transport_internet_hysteria_config_proto_rawDescOnce sync.Once
|
||||
file_transport_internet_hysteria_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_transport_internet_hysteria_config_proto_rawDescGZIP() []byte {
|
||||
file_transport_internet_hysteria_config_proto_rawDescOnce.Do(func() {
|
||||
file_transport_internet_hysteria_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)))
|
||||
})
|
||||
return file_transport_internet_hysteria_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_transport_internet_hysteria_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.transport.internet.hysteria.Config
|
||||
}
|
||||
var file_transport_internet_hysteria_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_hysteria_config_proto_init() }
|
||||
func file_transport_internet_hysteria_config_proto_init() {
|
||||
if File_transport_internet_hysteria_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_transport_internet_hysteria_config_proto_goTypes,
|
||||
DependencyIndexes: file_transport_internet_hysteria_config_proto_depIdxs,
|
||||
MessageInfos: file_transport_internet_hysteria_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_transport_internet_hysteria_config_proto = out.File
|
||||
file_transport_internet_hysteria_config_proto_goTypes = nil
|
||||
file_transport_internet_hysteria_config_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.transport.internet.hysteria;
|
||||
option csharp_namespace = "Xray.Transport.Internet.Hysteria";
|
||||
option go_package = "github.com/xtls/xray-core/transport/internet/hysteria";
|
||||
option java_package = "com.xray.transport.internet.hysteria";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Config {
|
||||
int32 version = 1;
|
||||
string auth = 2;
|
||||
string congestion = 3;
|
||||
uint64 up = 4;
|
||||
uint64 down = 5;
|
||||
repeated uint32 ports = 6;
|
||||
int64 interval_min = 7;
|
||||
int64 interval_max = 8;
|
||||
|
||||
uint64 init_stream_receive_window = 9;
|
||||
uint64 max_stream_receive_window = 10;
|
||||
uint64 init_conn_receive_window = 11;
|
||||
uint64 max_conn_receive_window = 12;
|
||||
int64 max_idle_timeout = 13;
|
||||
int64 keep_alive_period = 14;
|
||||
bool disable_path_mtu_discovery = 15;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package bbr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go/congestion"
|
||||
)
|
||||
|
||||
const (
|
||||
infBandwidth = Bandwidth(math.MaxUint64)
|
||||
)
|
||||
|
||||
// Bandwidth of a connection
|
||||
type Bandwidth uint64
|
||||
|
||||
const (
|
||||
// BitsPerSecond is 1 bit per second
|
||||
BitsPerSecond Bandwidth = 1
|
||||
// BytesPerSecond is 1 byte per second
|
||||
BytesPerSecond = 8 * BitsPerSecond
|
||||
)
|
||||
|
||||
// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta
|
||||
func BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth {
|
||||
return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond
|
||||
}
|
||||
@@ -0,0 +1,874 @@
|
||||
package bbr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go/congestion"
|
||||
)
|
||||
|
||||
const (
|
||||
infRTT = time.Duration(math.MaxInt64)
|
||||
defaultConnectionStateMapQueueSize = 256
|
||||
defaultCandidatesBufferSize = 256
|
||||
)
|
||||
|
||||
type roundTripCount uint64
|
||||
|
||||
// SendTimeState is a subset of ConnectionStateOnSentPacket which is returned
|
||||
// to the caller when the packet is acked or lost.
|
||||
type sendTimeState struct {
|
||||
// Whether other states in this object is valid.
|
||||
isValid bool
|
||||
// Whether the sender is app limited at the time the packet was sent.
|
||||
// App limited bandwidth sample might be artificially low because the sender
|
||||
// did not have enough data to send in order to saturate the link.
|
||||
isAppLimited bool
|
||||
// Total number of sent bytes at the time the packet was sent.
|
||||
// Includes the packet itself.
|
||||
totalBytesSent congestion.ByteCount
|
||||
// Total number of acked bytes at the time the packet was sent.
|
||||
totalBytesAcked congestion.ByteCount
|
||||
// Total number of lost bytes at the time the packet was sent.
|
||||
totalBytesLost congestion.ByteCount
|
||||
// Total number of inflight bytes at the time the packet was sent.
|
||||
// Includes the packet itself.
|
||||
// It should be equal to |total_bytes_sent| minus the sum of
|
||||
// |total_bytes_acked|, |total_bytes_lost| and total neutered bytes.
|
||||
bytesInFlight congestion.ByteCount
|
||||
}
|
||||
|
||||
func newSendTimeState(
|
||||
isAppLimited bool,
|
||||
totalBytesSent congestion.ByteCount,
|
||||
totalBytesAcked congestion.ByteCount,
|
||||
totalBytesLost congestion.ByteCount,
|
||||
bytesInFlight congestion.ByteCount,
|
||||
) *sendTimeState {
|
||||
return &sendTimeState{
|
||||
isValid: true,
|
||||
isAppLimited: isAppLimited,
|
||||
totalBytesSent: totalBytesSent,
|
||||
totalBytesAcked: totalBytesAcked,
|
||||
totalBytesLost: totalBytesLost,
|
||||
bytesInFlight: bytesInFlight,
|
||||
}
|
||||
}
|
||||
|
||||
type extraAckedEvent struct {
|
||||
// The excess bytes acknowlwedged in the time delta for this event.
|
||||
extraAcked congestion.ByteCount
|
||||
|
||||
// The bytes acknowledged and time delta from the event.
|
||||
bytesAcked congestion.ByteCount
|
||||
timeDelta time.Duration
|
||||
// The round trip of the event.
|
||||
round roundTripCount
|
||||
}
|
||||
|
||||
func maxExtraAckedEventFunc(a, b extraAckedEvent) int {
|
||||
if a.extraAcked > b.extraAcked {
|
||||
return 1
|
||||
} else if a.extraAcked < b.extraAcked {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// BandwidthSample
|
||||
type bandwidthSample struct {
|
||||
// The bandwidth at that particular sample. Zero if no valid bandwidth sample
|
||||
// is available.
|
||||
bandwidth Bandwidth
|
||||
// The RTT measurement at this particular sample. Zero if no RTT sample is
|
||||
// available. Does not correct for delayed ack time.
|
||||
rtt time.Duration
|
||||
// |send_rate| is computed from the current packet being acked('P') and an
|
||||
// earlier packet that is acked before P was sent.
|
||||
sendRate Bandwidth
|
||||
// States captured when the packet was sent.
|
||||
stateAtSend sendTimeState
|
||||
}
|
||||
|
||||
func newBandwidthSample() *bandwidthSample {
|
||||
return &bandwidthSample{
|
||||
sendRate: infBandwidth,
|
||||
}
|
||||
}
|
||||
|
||||
// MaxAckHeightTracker is part of the BandwidthSampler. It is called after every
|
||||
// ack event to keep track the degree of ack aggregation(a.k.a "ack height").
|
||||
type maxAckHeightTracker struct {
|
||||
// Tracks the maximum number of bytes acked faster than the estimated
|
||||
// bandwidth.
|
||||
maxAckHeightFilter *WindowedFilter[extraAckedEvent, roundTripCount]
|
||||
// The time this aggregation started and the number of bytes acked during it.
|
||||
aggregationEpochStartTime congestion.Time
|
||||
aggregationEpochBytes congestion.ByteCount
|
||||
// The last sent packet number before the current aggregation epoch started.
|
||||
lastSentPacketNumberBeforeEpoch congestion.PacketNumber
|
||||
// The number of ack aggregation epochs ever started, including the ongoing
|
||||
// one. Stats only.
|
||||
numAckAggregationEpochs uint64
|
||||
ackAggregationBandwidthThreshold float64
|
||||
startNewAggregationEpochAfterFullRound bool
|
||||
reduceExtraAckedOnBandwidthIncrease bool
|
||||
}
|
||||
|
||||
func newMaxAckHeightTracker(windowLength roundTripCount) *maxAckHeightTracker {
|
||||
return &maxAckHeightTracker{
|
||||
maxAckHeightFilter: NewWindowedFilter(windowLength, maxExtraAckedEventFunc),
|
||||
lastSentPacketNumberBeforeEpoch: invalidPacketNumber,
|
||||
ackAggregationBandwidthThreshold: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) Get() congestion.ByteCount {
|
||||
return m.maxAckHeightFilter.GetBest().extraAcked
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) Update(
|
||||
bandwidthEstimate Bandwidth,
|
||||
isNewMaxBandwidth bool,
|
||||
roundTripCount roundTripCount,
|
||||
lastSentPacketNumber congestion.PacketNumber,
|
||||
lastAckedPacketNumber congestion.PacketNumber,
|
||||
ackTime congestion.Time,
|
||||
bytesAcked congestion.ByteCount,
|
||||
) congestion.ByteCount {
|
||||
forceNewEpoch := false
|
||||
|
||||
if m.reduceExtraAckedOnBandwidthIncrease && isNewMaxBandwidth {
|
||||
// Save and clear existing entries.
|
||||
best := m.maxAckHeightFilter.GetBest()
|
||||
secondBest := m.maxAckHeightFilter.GetSecondBest()
|
||||
thirdBest := m.maxAckHeightFilter.GetThirdBest()
|
||||
m.maxAckHeightFilter.Clear()
|
||||
|
||||
// Reinsert the heights into the filter after recalculating.
|
||||
expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, best.timeDelta)
|
||||
if expectedBytesAcked < best.bytesAcked {
|
||||
best.extraAcked = best.bytesAcked - expectedBytesAcked
|
||||
m.maxAckHeightFilter.Update(best, best.round)
|
||||
}
|
||||
expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, secondBest.timeDelta)
|
||||
if expectedBytesAcked < secondBest.bytesAcked {
|
||||
secondBest.extraAcked = secondBest.bytesAcked - expectedBytesAcked
|
||||
m.maxAckHeightFilter.Update(secondBest, secondBest.round)
|
||||
}
|
||||
expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, thirdBest.timeDelta)
|
||||
if expectedBytesAcked < thirdBest.bytesAcked {
|
||||
thirdBest.extraAcked = thirdBest.bytesAcked - expectedBytesAcked
|
||||
m.maxAckHeightFilter.Update(thirdBest, thirdBest.round)
|
||||
}
|
||||
}
|
||||
|
||||
// If any packet sent after the start of the epoch has been acked, start a new
|
||||
// epoch.
|
||||
if m.startNewAggregationEpochAfterFullRound &&
|
||||
m.lastSentPacketNumberBeforeEpoch != invalidPacketNumber &&
|
||||
lastAckedPacketNumber != invalidPacketNumber &&
|
||||
lastAckedPacketNumber > m.lastSentPacketNumberBeforeEpoch {
|
||||
forceNewEpoch = true
|
||||
}
|
||||
if m.aggregationEpochStartTime.IsZero() || forceNewEpoch {
|
||||
m.aggregationEpochBytes = bytesAcked
|
||||
m.aggregationEpochStartTime = ackTime
|
||||
m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber
|
||||
m.numAckAggregationEpochs++
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute how many bytes are expected to be delivered, assuming max bandwidth
|
||||
// is correct.
|
||||
aggregationDelta := ackTime.Sub(m.aggregationEpochStartTime)
|
||||
expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, aggregationDelta)
|
||||
// Reset the current aggregation epoch as soon as the ack arrival rate is less
|
||||
// than or equal to the max bandwidth.
|
||||
if m.aggregationEpochBytes <= congestion.ByteCount(m.ackAggregationBandwidthThreshold*float64(expectedBytesAcked)) {
|
||||
// Reset to start measuring a new aggregation epoch.
|
||||
m.aggregationEpochBytes = bytesAcked
|
||||
m.aggregationEpochStartTime = ackTime
|
||||
m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber
|
||||
m.numAckAggregationEpochs++
|
||||
return 0
|
||||
}
|
||||
|
||||
m.aggregationEpochBytes += bytesAcked
|
||||
|
||||
// Compute how many extra bytes were delivered vs max bandwidth.
|
||||
extraBytesAcked := m.aggregationEpochBytes - expectedBytesAcked
|
||||
newEvent := extraAckedEvent{
|
||||
extraAcked: extraBytesAcked,
|
||||
bytesAcked: m.aggregationEpochBytes,
|
||||
timeDelta: aggregationDelta,
|
||||
}
|
||||
m.maxAckHeightFilter.Update(newEvent, roundTripCount)
|
||||
return extraBytesAcked
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) SetFilterWindowLength(length roundTripCount) {
|
||||
m.maxAckHeightFilter.SetWindowLength(length)
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) Reset(newHeight congestion.ByteCount, newTime roundTripCount) {
|
||||
newEvent := extraAckedEvent{
|
||||
extraAcked: newHeight,
|
||||
round: newTime,
|
||||
}
|
||||
m.maxAckHeightFilter.Reset(newEvent, newTime)
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) SetAckAggregationBandwidthThreshold(threshold float64) {
|
||||
m.ackAggregationBandwidthThreshold = threshold
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) SetStartNewAggregationEpochAfterFullRound(value bool) {
|
||||
m.startNewAggregationEpochAfterFullRound = value
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) SetReduceExtraAckedOnBandwidthIncrease(value bool) {
|
||||
m.reduceExtraAckedOnBandwidthIncrease = value
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) AckAggregationBandwidthThreshold() float64 {
|
||||
return m.ackAggregationBandwidthThreshold
|
||||
}
|
||||
|
||||
func (m *maxAckHeightTracker) NumAckAggregationEpochs() uint64 {
|
||||
return m.numAckAggregationEpochs
|
||||
}
|
||||
|
||||
// AckPoint represents a point on the ack line.
|
||||
type ackPoint struct {
|
||||
ackTime congestion.Time
|
||||
totalBytesAcked congestion.ByteCount
|
||||
}
|
||||
|
||||
// RecentAckPoints maintains the most recent 2 ack points at distinct times.
|
||||
type recentAckPoints struct {
|
||||
ackPoints [2]ackPoint
|
||||
}
|
||||
|
||||
func (r *recentAckPoints) Update(ackTime congestion.Time, totalBytesAcked congestion.ByteCount) {
|
||||
if ackTime.Before(r.ackPoints[1].ackTime) {
|
||||
r.ackPoints[1].ackTime = ackTime
|
||||
} else if ackTime.After(r.ackPoints[1].ackTime) {
|
||||
r.ackPoints[0] = r.ackPoints[1]
|
||||
r.ackPoints[1].ackTime = ackTime
|
||||
}
|
||||
|
||||
r.ackPoints[1].totalBytesAcked = totalBytesAcked
|
||||
}
|
||||
|
||||
func (r *recentAckPoints) Clear() {
|
||||
r.ackPoints[0] = ackPoint{}
|
||||
r.ackPoints[1] = ackPoint{}
|
||||
}
|
||||
|
||||
func (r *recentAckPoints) MostRecentPoint() *ackPoint {
|
||||
return &r.ackPoints[1]
|
||||
}
|
||||
|
||||
func (r *recentAckPoints) LessRecentPoint() *ackPoint {
|
||||
if r.ackPoints[0].totalBytesAcked != 0 {
|
||||
return &r.ackPoints[0]
|
||||
}
|
||||
|
||||
return &r.ackPoints[1]
|
||||
}
|
||||
|
||||
// ConnectionStateOnSentPacket represents the information about a sent packet
|
||||
// and the state of the connection at the moment the packet was sent,
|
||||
// specifically the information about the most recently acknowledged packet at
|
||||
// that moment.
|
||||
type connectionStateOnSentPacket struct {
|
||||
// Time at which the packet is sent.
|
||||
sentTime congestion.Time
|
||||
// Size of the packet.
|
||||
size congestion.ByteCount
|
||||
// The value of |totalBytesSentAtLastAckedPacket| at the time the
|
||||
// packet was sent.
|
||||
totalBytesSentAtLastAckedPacket congestion.ByteCount
|
||||
// The value of |lastAckedPacketSentTime| at the time the packet was
|
||||
// sent.
|
||||
lastAckedPacketSentTime congestion.Time
|
||||
// The value of |lastAckedPacketAckTime| at the time the packet was
|
||||
// sent.
|
||||
lastAckedPacketAckTime congestion.Time
|
||||
// Send time states that are returned to the congestion controller when the
|
||||
// packet is acked or lost.
|
||||
sendTimeState sendTimeState
|
||||
}
|
||||
|
||||
// Snapshot constructor. Records the current state of the bandwidth
|
||||
// sampler.
|
||||
// |bytes_in_flight| is the bytes in flight right after the packet is sent.
|
||||
func newConnectionStateOnSentPacket(
|
||||
sentTime congestion.Time,
|
||||
size congestion.ByteCount,
|
||||
bytesInFlight congestion.ByteCount,
|
||||
sampler *bandwidthSampler,
|
||||
) *connectionStateOnSentPacket {
|
||||
return &connectionStateOnSentPacket{
|
||||
sentTime: sentTime,
|
||||
size: size,
|
||||
totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket,
|
||||
lastAckedPacketSentTime: sampler.lastAckedPacketSentTime,
|
||||
lastAckedPacketAckTime: sampler.lastAckedPacketAckTime,
|
||||
sendTimeState: *newSendTimeState(
|
||||
sampler.isAppLimited,
|
||||
sampler.totalBytesSent,
|
||||
sampler.totalBytesAcked,
|
||||
sampler.totalBytesLost,
|
||||
bytesInFlight,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// BandwidthSampler keeps track of sent and acknowledged packets and outputs a
|
||||
// bandwidth sample for every packet acknowledged. The samples are taken for
|
||||
// individual packets, and are not filtered; the consumer has to filter the
|
||||
// bandwidth samples itself. In certain cases, the sampler will locally severely
|
||||
// underestimate the bandwidth, hence a maximum filter with a size of at least
|
||||
// one RTT is recommended.
|
||||
//
|
||||
// This class bases its samples on the slope of two curves: the number of bytes
|
||||
// sent over time, and the number of bytes acknowledged as received over time.
|
||||
// It produces a sample of both slopes for every packet that gets acknowledged,
|
||||
// based on a slope between two points on each of the corresponding curves. Note
|
||||
// that due to the packet loss, the number of bytes on each curve might get
|
||||
// further and further away from each other, meaning that it is not feasible to
|
||||
// compare byte values coming from different curves with each other.
|
||||
//
|
||||
// The obvious points for measuring slope sample are the ones corresponding to
|
||||
// the packet that was just acknowledged. Let us denote them as S_1 (point at
|
||||
// which the current packet was sent) and A_1 (point at which the current packet
|
||||
// was acknowledged). However, taking a slope requires two points on each line,
|
||||
// so estimating bandwidth requires picking a packet in the past with respect to
|
||||
// which the slope is measured.
|
||||
//
|
||||
// For that purpose, BandwidthSampler always keeps track of the most recently
|
||||
// acknowledged packet, and records it together with every outgoing packet.
|
||||
// When a packet gets acknowledged (A_1), it has not only information about when
|
||||
// it itself was sent (S_1), but also the information about the latest
|
||||
// acknowledged packet right before it was sent (S_0 and A_0).
|
||||
//
|
||||
// Based on that data, send and ack rate are estimated as:
|
||||
//
|
||||
// send_rate = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0))
|
||||
// ack_rate = (bytes(A_1) - bytes(A_0)) / (time(A_1) - time(A_0))
|
||||
//
|
||||
// Here, the ack rate is intuitively the rate we want to treat as bandwidth.
|
||||
// However, in certain cases (e.g. ack compression) the ack rate at a point may
|
||||
// end up higher than the rate at which the data was originally sent, which is
|
||||
// not indicative of the real bandwidth. Hence, we use the send rate as an upper
|
||||
// bound, and the sample value is
|
||||
//
|
||||
// rate_sample = min(send_rate, ack_rate)
|
||||
//
|
||||
// An important edge case handled by the sampler is tracking the app-limited
|
||||
// samples. There are multiple meaning of "app-limited" used interchangeably,
|
||||
// hence it is important to understand and to be able to distinguish between
|
||||
// them.
|
||||
//
|
||||
// Meaning 1: connection state. The connection is said to be app-limited when
|
||||
// there is no outstanding data to send. This means that certain bandwidth
|
||||
// samples in the future would not be an accurate indication of the link
|
||||
// capacity, and it is important to inform consumer about that. Whenever
|
||||
// connection becomes app-limited, the sampler is notified via OnAppLimited()
|
||||
// method.
|
||||
//
|
||||
// Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth
|
||||
// sampler becomes notified about the connection being app-limited, it enters
|
||||
// app-limited phase. In that phase, all *sent* packets are marked as
|
||||
// app-limited. Note that the connection itself does not have to be
|
||||
// app-limited during the app-limited phase, and in fact it will not be
|
||||
// (otherwise how would it send packets?). The boolean flag below indicates
|
||||
// whether the sampler is in that phase.
|
||||
//
|
||||
// Meaning 3: a flag on the sent packet and on the sample. If a sent packet is
|
||||
// sent during the app-limited phase, the resulting sample related to the
|
||||
// packet will be marked as app-limited.
|
||||
//
|
||||
// With the terminology issue out of the way, let us consider the question of
|
||||
// what kind of situation it addresses.
|
||||
//
|
||||
// Consider a scenario where we first send packets 1 to 20 at a regular
|
||||
// bandwidth, and then immediately run out of data. After a few seconds, we send
|
||||
// packets 21 to 60, and only receive ack for 21 between sending packets 40 and
|
||||
// 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0
|
||||
// we use to compute the slope is going to be packet 20, a few seconds apart
|
||||
// from the current packet, hence the resulting estimate would be extremely low
|
||||
// and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21,
|
||||
// meaning that the bandwidth sample would exclude the quiescence.
|
||||
//
|
||||
// Based on the analysis of that scenario, we implement the following rule: once
|
||||
// OnAppLimited() is called, all sent packets will produce app-limited samples
|
||||
// up until an ack for a packet that was sent after OnAppLimited() was called.
|
||||
// Note that while the scenario above is not the only scenario when the
|
||||
// connection is app-limited, the approach works in other cases too.
|
||||
|
||||
type congestionEventSample struct {
|
||||
// The maximum bandwidth sample from all acked packets.
|
||||
// QuicBandwidth::Zero() if no samples are available.
|
||||
sampleMaxBandwidth Bandwidth
|
||||
// Whether |sample_max_bandwidth| is from a app-limited sample.
|
||||
sampleIsAppLimited bool
|
||||
// The minimum rtt sample from all acked packets.
|
||||
// QuicTime::Delta::Infinite() if no samples are available.
|
||||
sampleRtt time.Duration
|
||||
// For each packet p in acked packets, this is the max value of INFLIGHT(p),
|
||||
// where INFLIGHT(p) is the number of bytes acked while p is inflight.
|
||||
sampleMaxInflight congestion.ByteCount
|
||||
// The send state of the largest packet in acked_packets, unless it is
|
||||
// empty. If acked_packets is empty, it's the send state of the largest
|
||||
// packet in lost_packets.
|
||||
lastPacketSendState sendTimeState
|
||||
// The number of extra bytes acked from this ack event, compared to what is
|
||||
// expected from the flow's bandwidth. Larger value means more ack
|
||||
// aggregation.
|
||||
extraAcked congestion.ByteCount
|
||||
}
|
||||
|
||||
func newCongestionEventSample() *congestionEventSample {
|
||||
return &congestionEventSample{
|
||||
sampleRtt: infRTT,
|
||||
}
|
||||
}
|
||||
|
||||
type bandwidthSampler struct {
|
||||
// The total number of congestion controlled bytes sent during the connection.
|
||||
totalBytesSent congestion.ByteCount
|
||||
|
||||
// The total number of congestion controlled bytes which were acknowledged.
|
||||
totalBytesAcked congestion.ByteCount
|
||||
|
||||
// The total number of congestion controlled bytes which were lost.
|
||||
totalBytesLost congestion.ByteCount
|
||||
|
||||
// The total number of congestion controlled bytes which have been neutered.
|
||||
totalBytesNeutered congestion.ByteCount
|
||||
|
||||
// The value of |total_bytes_sent_| at the time the last acknowledged packet
|
||||
// was sent. Valid only when |last_acked_packet_sent_time_| is valid.
|
||||
totalBytesSentAtLastAckedPacket congestion.ByteCount
|
||||
|
||||
// The time at which the last acknowledged packet was sent. Set to
|
||||
// QuicTime::Zero() if no valid timestamp is available.
|
||||
lastAckedPacketSentTime congestion.Time
|
||||
|
||||
// The time at which the most recent packet was acknowledged.
|
||||
lastAckedPacketAckTime congestion.Time
|
||||
|
||||
// The most recently sent packet.
|
||||
lastSentPacket congestion.PacketNumber
|
||||
|
||||
// The most recently acked packet.
|
||||
lastAckedPacket congestion.PacketNumber
|
||||
|
||||
// Indicates whether the bandwidth sampler is currently in an app-limited
|
||||
// phase.
|
||||
isAppLimited bool
|
||||
|
||||
// The packet that will be acknowledged after this one will cause the sampler
|
||||
// to exit the app-limited phase.
|
||||
endOfAppLimitedPhase congestion.PacketNumber
|
||||
|
||||
// Record of the connection state at the point where each packet in flight was
|
||||
// sent, indexed by the packet number.
|
||||
connectionStateMap *packetNumberIndexedQueue[connectionStateOnSentPacket]
|
||||
|
||||
recentAckPoints recentAckPoints
|
||||
a0Candidates RingBuffer[ackPoint]
|
||||
|
||||
// Maximum number of tracked packets.
|
||||
maxTrackedPackets congestion.ByteCount
|
||||
|
||||
maxAckHeightTracker *maxAckHeightTracker
|
||||
totalBytesAckedAfterLastAckEvent congestion.ByteCount
|
||||
|
||||
// True if connection option 'BSAO' is set.
|
||||
overestimateAvoidance bool
|
||||
|
||||
// True if connection option 'BBRB' is set.
|
||||
limitMaxAckHeightTrackerBySendRate bool
|
||||
}
|
||||
|
||||
func newBandwidthSampler(maxAckHeightTrackerWindowLength roundTripCount) *bandwidthSampler {
|
||||
b := &bandwidthSampler{
|
||||
maxAckHeightTracker: newMaxAckHeightTracker(maxAckHeightTrackerWindowLength),
|
||||
connectionStateMap: newPacketNumberIndexedQueue[connectionStateOnSentPacket](defaultConnectionStateMapQueueSize),
|
||||
lastSentPacket: invalidPacketNumber,
|
||||
lastAckedPacket: invalidPacketNumber,
|
||||
endOfAppLimitedPhase: invalidPacketNumber,
|
||||
}
|
||||
|
||||
b.a0Candidates.Init(defaultCandidatesBufferSize)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) MaxAckHeight() congestion.ByteCount {
|
||||
return b.maxAckHeightTracker.Get()
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) NumAckAggregationEpochs() uint64 {
|
||||
return b.maxAckHeightTracker.NumAckAggregationEpochs()
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) SetMaxAckHeightTrackerWindowLength(length roundTripCount) {
|
||||
b.maxAckHeightTracker.SetFilterWindowLength(length)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) ResetMaxAckHeightTracker(newHeight congestion.ByteCount, newTime roundTripCount) {
|
||||
b.maxAckHeightTracker.Reset(newHeight, newTime)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) SetStartNewAggregationEpochAfterFullRound(value bool) {
|
||||
b.maxAckHeightTracker.SetStartNewAggregationEpochAfterFullRound(value)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) SetLimitMaxAckHeightTrackerBySendRate(value bool) {
|
||||
b.limitMaxAckHeightTrackerBySendRate = value
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) SetReduceExtraAckedOnBandwidthIncrease(value bool) {
|
||||
b.maxAckHeightTracker.SetReduceExtraAckedOnBandwidthIncrease(value)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) EnableOverestimateAvoidance() {
|
||||
if b.overestimateAvoidance {
|
||||
return
|
||||
}
|
||||
|
||||
b.overestimateAvoidance = true
|
||||
b.maxAckHeightTracker.SetAckAggregationBandwidthThreshold(2.0)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) IsOverestimateAvoidanceEnabled() bool {
|
||||
return b.overestimateAvoidance
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) OnPacketSent(
|
||||
sentTime congestion.Time,
|
||||
packetNumber congestion.PacketNumber,
|
||||
bytes congestion.ByteCount,
|
||||
bytesInFlight congestion.ByteCount,
|
||||
isRetransmittable bool,
|
||||
) {
|
||||
b.lastSentPacket = packetNumber
|
||||
|
||||
if !isRetransmittable {
|
||||
return
|
||||
}
|
||||
|
||||
b.totalBytesSent += bytes
|
||||
|
||||
// If there are no packets in flight, the time at which the new transmission
|
||||
// opens can be treated as the A_0 point for the purpose of bandwidth
|
||||
// sampling. This underestimates bandwidth to some extent, and produces some
|
||||
// artificially low samples for most packets in flight, but it provides with
|
||||
// samples at important points where we would not have them otherwise, most
|
||||
// importantly at the beginning of the connection.
|
||||
if bytesInFlight == 0 {
|
||||
b.lastAckedPacketAckTime = sentTime
|
||||
if b.overestimateAvoidance {
|
||||
b.recentAckPoints.Clear()
|
||||
b.recentAckPoints.Update(sentTime, b.totalBytesAcked)
|
||||
b.a0Candidates.Clear()
|
||||
b.a0Candidates.PushBack(*b.recentAckPoints.MostRecentPoint())
|
||||
}
|
||||
b.totalBytesSentAtLastAckedPacket = b.totalBytesSent
|
||||
|
||||
// In this situation ack compression is not a concern, set send rate to
|
||||
// effectively infinite.
|
||||
b.lastAckedPacketSentTime = sentTime
|
||||
}
|
||||
|
||||
b.connectionStateMap.Emplace(packetNumber, newConnectionStateOnSentPacket(
|
||||
sentTime,
|
||||
bytes,
|
||||
bytesInFlight+bytes,
|
||||
b,
|
||||
))
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) OnCongestionEvent(
|
||||
ackTime congestion.Time,
|
||||
ackedPackets []congestion.AckedPacketInfo,
|
||||
lostPackets []congestion.LostPacketInfo,
|
||||
maxBandwidth Bandwidth,
|
||||
estBandwidthUpperBound Bandwidth,
|
||||
roundTripCount roundTripCount,
|
||||
) congestionEventSample {
|
||||
eventSample := newCongestionEventSample()
|
||||
|
||||
var lastLostPacketSendState sendTimeState
|
||||
|
||||
for _, p := range lostPackets {
|
||||
sendState := b.OnPacketLost(p.PacketNumber, p.BytesLost)
|
||||
if sendState.isValid {
|
||||
lastLostPacketSendState = sendState
|
||||
}
|
||||
}
|
||||
|
||||
if len(ackedPackets) == 0 {
|
||||
// Only populate send state for a loss-only event.
|
||||
eventSample.lastPacketSendState = lastLostPacketSendState
|
||||
return *eventSample
|
||||
}
|
||||
|
||||
var lastAckedPacketSendState sendTimeState
|
||||
var maxSendRate Bandwidth
|
||||
|
||||
for _, p := range ackedPackets {
|
||||
sample := b.onPacketAcknowledged(ackTime, p.PacketNumber)
|
||||
if !sample.stateAtSend.isValid {
|
||||
continue
|
||||
}
|
||||
|
||||
lastAckedPacketSendState = sample.stateAtSend
|
||||
|
||||
if sample.rtt != 0 {
|
||||
eventSample.sampleRtt = min(eventSample.sampleRtt, sample.rtt)
|
||||
}
|
||||
if sample.bandwidth > eventSample.sampleMaxBandwidth {
|
||||
eventSample.sampleMaxBandwidth = sample.bandwidth
|
||||
eventSample.sampleIsAppLimited = sample.stateAtSend.isAppLimited
|
||||
}
|
||||
if sample.sendRate != infBandwidth {
|
||||
maxSendRate = max(maxSendRate, sample.sendRate)
|
||||
}
|
||||
inflightSample := b.totalBytesAcked - lastAckedPacketSendState.totalBytesAcked
|
||||
if inflightSample > eventSample.sampleMaxInflight {
|
||||
eventSample.sampleMaxInflight = inflightSample
|
||||
}
|
||||
}
|
||||
|
||||
if !lastLostPacketSendState.isValid {
|
||||
eventSample.lastPacketSendState = lastAckedPacketSendState
|
||||
} else if !lastAckedPacketSendState.isValid {
|
||||
eventSample.lastPacketSendState = lastLostPacketSendState
|
||||
} else {
|
||||
// If two packets are inflight and an alarm is armed to lose a packet and it
|
||||
// wakes up late, then the first of two in flight packets could have been
|
||||
// acknowledged before the wakeup, which re-evaluates loss detection, and
|
||||
// could declare the later of the two lost.
|
||||
if lostPackets[len(lostPackets)-1].PacketNumber > ackedPackets[len(ackedPackets)-1].PacketNumber {
|
||||
eventSample.lastPacketSendState = lastLostPacketSendState
|
||||
} else {
|
||||
eventSample.lastPacketSendState = lastAckedPacketSendState
|
||||
}
|
||||
}
|
||||
|
||||
isNewMaxBandwidth := eventSample.sampleMaxBandwidth > maxBandwidth
|
||||
maxBandwidth = max(maxBandwidth, eventSample.sampleMaxBandwidth)
|
||||
if b.limitMaxAckHeightTrackerBySendRate {
|
||||
maxBandwidth = max(maxBandwidth, maxSendRate)
|
||||
}
|
||||
|
||||
eventSample.extraAcked = b.onAckEventEnd(min(estBandwidthUpperBound, maxBandwidth), isNewMaxBandwidth, roundTripCount)
|
||||
|
||||
return *eventSample
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber, bytesLost congestion.ByteCount) (s sendTimeState) {
|
||||
b.totalBytesLost += bytesLost
|
||||
if sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber); sentPacketPointer != nil {
|
||||
sentPacketToSendTimeState(sentPacketPointer, &s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) OnPacketNeutered(packetNumber congestion.PacketNumber) {
|
||||
b.connectionStateMap.Remove(packetNumber, func(sentPacket connectionStateOnSentPacket) {
|
||||
b.totalBytesNeutered += sentPacket.size
|
||||
})
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) OnAppLimited() {
|
||||
b.isAppLimited = true
|
||||
b.endOfAppLimitedPhase = b.lastSentPacket
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) RemoveObsoletePackets(leastUnacked congestion.PacketNumber) {
|
||||
// A packet can become obsolete when it is removed from QuicUnackedPacketMap's
|
||||
// view of inflight before it is acked or marked as lost. For example, when
|
||||
// QuicSentPacketManager::RetransmitCryptoPackets retransmits a crypto packet,
|
||||
// the packet is removed from QuicUnackedPacketMap's inflight, but is not
|
||||
// marked as acked or lost in the BandwidthSampler.
|
||||
b.connectionStateMap.RemoveUpTo(leastUnacked)
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) TotalBytesSent() congestion.ByteCount {
|
||||
return b.totalBytesSent
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) TotalBytesLost() congestion.ByteCount {
|
||||
return b.totalBytesLost
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) TotalBytesAcked() congestion.ByteCount {
|
||||
return b.totalBytesAcked
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) TotalBytesNeutered() congestion.ByteCount {
|
||||
return b.totalBytesNeutered
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) IsAppLimited() bool {
|
||||
return b.isAppLimited
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) EndOfAppLimitedPhase() congestion.PacketNumber {
|
||||
return b.endOfAppLimitedPhase
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) max_ack_height() congestion.ByteCount {
|
||||
return b.maxAckHeightTracker.Get()
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) chooseA0Point(totalBytesAcked congestion.ByteCount, a0 *ackPoint) bool {
|
||||
if b.a0Candidates.Empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.a0Candidates.Len() == 1 {
|
||||
*a0 = *b.a0Candidates.Front()
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 1; i < b.a0Candidates.Len(); i++ {
|
||||
if b.a0Candidates.Offset(i).totalBytesAcked > totalBytesAcked {
|
||||
*a0 = *b.a0Candidates.Offset(i - 1)
|
||||
if i > 1 {
|
||||
for j := 0; j < i-1; j++ {
|
||||
b.a0Candidates.PopFront()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
*a0 = *b.a0Candidates.Back()
|
||||
for k := 0; k < b.a0Candidates.Len()-1; k++ {
|
||||
b.a0Candidates.PopFront()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) onPacketAcknowledged(ackTime congestion.Time, packetNumber congestion.PacketNumber) bandwidthSample {
|
||||
sample := newBandwidthSample()
|
||||
b.lastAckedPacket = packetNumber
|
||||
sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber)
|
||||
if sentPacketPointer == nil {
|
||||
return *sample
|
||||
}
|
||||
|
||||
// OnPacketAcknowledgedInner
|
||||
b.totalBytesAcked += sentPacketPointer.size
|
||||
b.totalBytesSentAtLastAckedPacket = sentPacketPointer.sendTimeState.totalBytesSent
|
||||
b.lastAckedPacketSentTime = sentPacketPointer.sentTime
|
||||
b.lastAckedPacketAckTime = ackTime
|
||||
if b.overestimateAvoidance {
|
||||
b.recentAckPoints.Update(ackTime, b.totalBytesAcked)
|
||||
}
|
||||
|
||||
if b.isAppLimited {
|
||||
// Exit app-limited phase in two cases:
|
||||
// (1) end_of_app_limited_phase_ is not initialized, i.e., so far all
|
||||
// packets are sent while there are buffered packets or pending data.
|
||||
// (2) The current acked packet is after the sent packet marked as the end
|
||||
// of the app limit phase.
|
||||
if b.endOfAppLimitedPhase == invalidPacketNumber ||
|
||||
packetNumber > b.endOfAppLimitedPhase {
|
||||
b.isAppLimited = false
|
||||
}
|
||||
}
|
||||
|
||||
// There might have been no packets acknowledged at the moment when the
|
||||
// current packet was sent. In that case, there is no bandwidth sample to
|
||||
// make.
|
||||
if sentPacketPointer.lastAckedPacketSentTime.IsZero() {
|
||||
return *sample
|
||||
}
|
||||
|
||||
// Infinite rate indicates that the sampler is supposed to discard the
|
||||
// current send rate sample and use only the ack rate.
|
||||
sendRate := infBandwidth
|
||||
if sentPacketPointer.sentTime.After(sentPacketPointer.lastAckedPacketSentTime) {
|
||||
sendRate = BandwidthFromDelta(
|
||||
sentPacketPointer.sendTimeState.totalBytesSent-sentPacketPointer.totalBytesSentAtLastAckedPacket,
|
||||
sentPacketPointer.sentTime.Sub(sentPacketPointer.lastAckedPacketSentTime))
|
||||
}
|
||||
|
||||
var a0 ackPoint
|
||||
if b.overestimateAvoidance && b.chooseA0Point(sentPacketPointer.sendTimeState.totalBytesAcked, &a0) {
|
||||
} else {
|
||||
a0.ackTime = sentPacketPointer.lastAckedPacketAckTime
|
||||
a0.totalBytesAcked = sentPacketPointer.sendTimeState.totalBytesAcked
|
||||
}
|
||||
|
||||
// During the slope calculation, ensure that ack time of the current packet is
|
||||
// always larger than the time of the previous packet, otherwise division by
|
||||
// zero or integer underflow can occur.
|
||||
if ackTime.Sub(a0.ackTime) <= 0 {
|
||||
return *sample
|
||||
}
|
||||
|
||||
ackRate := BandwidthFromDelta(b.totalBytesAcked-a0.totalBytesAcked, ackTime.Sub(a0.ackTime))
|
||||
|
||||
sample.bandwidth = min(sendRate, ackRate)
|
||||
// Note: this sample does not account for delayed acknowledgement time. This
|
||||
// means that the RTT measurements here can be artificially high, especially
|
||||
// on low bandwidth connections.
|
||||
sample.rtt = ackTime.Sub(sentPacketPointer.sentTime)
|
||||
sample.sendRate = sendRate
|
||||
sentPacketToSendTimeState(sentPacketPointer, &sample.stateAtSend)
|
||||
|
||||
return *sample
|
||||
}
|
||||
|
||||
func (b *bandwidthSampler) onAckEventEnd(
|
||||
bandwidthEstimate Bandwidth,
|
||||
isNewMaxBandwidth bool,
|
||||
roundTripCount roundTripCount,
|
||||
) congestion.ByteCount {
|
||||
newlyAckedBytes := b.totalBytesAcked - b.totalBytesAckedAfterLastAckEvent
|
||||
if newlyAckedBytes == 0 {
|
||||
return 0
|
||||
}
|
||||
b.totalBytesAckedAfterLastAckEvent = b.totalBytesAcked
|
||||
extraAcked := b.maxAckHeightTracker.Update(
|
||||
bandwidthEstimate,
|
||||
isNewMaxBandwidth,
|
||||
roundTripCount,
|
||||
b.lastSentPacket,
|
||||
b.lastAckedPacket,
|
||||
b.lastAckedPacketAckTime,
|
||||
newlyAckedBytes)
|
||||
// If |extra_acked| is zero, i.e. this ack event marks the start of a new ack
|
||||
// aggregation epoch, save LessRecentPoint, which is the last ack point of the
|
||||
// previous epoch, as a A0 candidate.
|
||||
if b.overestimateAvoidance && extraAcked == 0 {
|
||||
b.a0Candidates.PushBack(*b.recentAckPoints.LessRecentPoint())
|
||||
}
|
||||
return extraAcked
|
||||
}
|
||||
|
||||
func sentPacketToSendTimeState(sentPacket *connectionStateOnSentPacket, sendTimeState *sendTimeState) {
|
||||
*sendTimeState = sentPacket.sendTimeState
|
||||
sendTimeState.isValid = true
|
||||
}
|
||||
|
||||
// BytesFromBandwidthAndTimeDelta calculates the bytes
|
||||
// from a bandwidth(bits per second) and a time delta
|
||||
func bytesFromBandwidthAndTimeDelta(bandwidth Bandwidth, delta time.Duration) congestion.ByteCount {
|
||||
return (congestion.ByteCount(bandwidth) * congestion.ByteCount(delta)) /
|
||||
(congestion.ByteCount(time.Second) * 8)
|
||||
}
|
||||
|
||||
func timeDeltaFromBytesAndBandwidth(bytes congestion.ByteCount, bandwidth Bandwidth) time.Duration {
|
||||
return time.Duration(bytes*8) * time.Second / time.Duration(bandwidth)
|
||||
}
|
||||
@@ -0,0 +1,980 @@
|
||||
package bbr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go/congestion"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/common"
|
||||
)
|
||||
|
||||
// BbrSender implements BBR congestion control algorithm. BBR aims to estimate
|
||||
// the current available Bottleneck Bandwidth and RTT (hence the name), and
|
||||
// regulates the pacing rate and the size of the congestion window based on
|
||||
// those signals.
|
||||
//
|
||||
// BBR relies on pacing in order to function properly. Do not use BBR when
|
||||
// pacing is disabled.
|
||||
//
|
||||
|
||||
const (
|
||||
minBps = 65536 // 64 KB/s
|
||||
|
||||
invalidPacketNumber = -1
|
||||
initialCongestionWindowPackets = 32
|
||||
|
||||
// Constants based on TCP defaults.
|
||||
// The minimum CWND to ensure delayed acks don't reduce bandwidth measurements.
|
||||
// Does not inflate the pacing rate.
|
||||
defaultMinimumCongestionWindow = 4 * congestion.ByteCount(congestion.InitialPacketSize)
|
||||
|
||||
// The gain used for the STARTUP, equal to 2/ln(2).
|
||||
defaultHighGain = 2.885
|
||||
// The newly derived gain for STARTUP, equal to 4 * ln(2)
|
||||
derivedHighGain = 2.773
|
||||
// The newly derived CWND gain for STARTUP, 2.
|
||||
derivedHighCWNDGain = 2.0
|
||||
|
||||
debugEnv = "HYSTERIA_BBR_DEBUG"
|
||||
)
|
||||
|
||||
// The cycle of gains used during the PROBE_BW stage.
|
||||
var pacingGain = [...]float64{1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}
|
||||
|
||||
const (
|
||||
// The length of the gain cycle.
|
||||
gainCycleLength = len(pacingGain)
|
||||
// The size of the bandwidth filter window, in round-trips.
|
||||
bandwidthWindowSize = gainCycleLength + 2
|
||||
|
||||
// The time after which the current min_rtt value expires.
|
||||
minRttExpiry = 10 * time.Second
|
||||
// The minimum time the connection can spend in PROBE_RTT mode.
|
||||
probeRttTime = 200 * time.Millisecond
|
||||
// If the bandwidth does not increase by the factor of |kStartupGrowthTarget|
|
||||
// within |kRoundTripsWithoutGrowthBeforeExitingStartup| rounds, the connection
|
||||
// will exit the STARTUP mode.
|
||||
startupGrowthTarget = 1.25
|
||||
roundTripsWithoutGrowthBeforeExitingStartup = int64(3)
|
||||
|
||||
// Flag.
|
||||
defaultStartupFullLossCount = 8
|
||||
quicBbr2DefaultLossThreshold = 0.02
|
||||
maxBbrBurstPackets = 10
|
||||
)
|
||||
|
||||
type bbrMode int
|
||||
|
||||
const (
|
||||
// Startup phase of the connection.
|
||||
bbrModeStartup = iota
|
||||
// After achieving the highest possible bandwidth during the startup, lower
|
||||
// the pacing rate in order to drain the queue.
|
||||
bbrModeDrain
|
||||
// Cruising mode.
|
||||
bbrModeProbeBw
|
||||
// Temporarily slow down sending in order to empty the buffer and measure
|
||||
// the real minimum RTT.
|
||||
bbrModeProbeRtt
|
||||
)
|
||||
|
||||
// Indicates how the congestion control limits the amount of bytes in flight.
|
||||
type bbrRecoveryState int
|
||||
|
||||
const (
|
||||
// Do not limit.
|
||||
bbrRecoveryStateNotInRecovery = iota
|
||||
// Allow an extra outstanding byte for each byte acknowledged.
|
||||
bbrRecoveryStateConservation
|
||||
// Allow two extra outstanding bytes for each byte acknowledged (slow
|
||||
// start).
|
||||
bbrRecoveryStateGrowth
|
||||
)
|
||||
|
||||
type bbrSender struct {
|
||||
rttStats congestion.RTTStatsProvider
|
||||
clock Clock
|
||||
pacer *common.Pacer
|
||||
|
||||
mode bbrMode
|
||||
|
||||
// Bandwidth sampler provides BBR with the bandwidth measurements at
|
||||
// individual points.
|
||||
sampler *bandwidthSampler
|
||||
|
||||
// The number of the round trips that have occurred during the connection.
|
||||
roundTripCount roundTripCount
|
||||
|
||||
// The packet number of the most recently sent packet.
|
||||
lastSentPacket congestion.PacketNumber
|
||||
// Acknowledgement of any packet after |current_round_trip_end_| will cause
|
||||
// the round trip counter to advance.
|
||||
currentRoundTripEnd congestion.PacketNumber
|
||||
|
||||
// Number of congestion events with some losses, in the current round.
|
||||
numLossEventsInRound uint64
|
||||
|
||||
// Number of total bytes lost in the current round.
|
||||
bytesLostInRound congestion.ByteCount
|
||||
|
||||
// The filter that tracks the maximum bandwidth over the multiple recent
|
||||
// round-trips.
|
||||
maxBandwidth *WindowedFilter[Bandwidth, roundTripCount]
|
||||
|
||||
// Minimum RTT estimate. Automatically expires within 10 seconds (and
|
||||
// triggers PROBE_RTT mode) if no new value is sampled during that period.
|
||||
minRtt time.Duration
|
||||
// The time at which the current value of |min_rtt_| was assigned.
|
||||
minRttTimestamp congestion.Time
|
||||
|
||||
// The maximum allowed number of bytes in flight.
|
||||
congestionWindow congestion.ByteCount
|
||||
|
||||
// The initial value of the |congestion_window_|.
|
||||
initialCongestionWindow congestion.ByteCount
|
||||
|
||||
// The largest value the |congestion_window_| can achieve.
|
||||
maxCongestionWindow congestion.ByteCount
|
||||
|
||||
// The smallest value the |congestion_window_| can achieve.
|
||||
minCongestionWindow congestion.ByteCount
|
||||
|
||||
// The pacing gain applied during the STARTUP phase.
|
||||
highGain float64
|
||||
|
||||
// The CWND gain applied during the STARTUP phase.
|
||||
highCwndGain float64
|
||||
|
||||
// The pacing gain applied during the DRAIN phase.
|
||||
drainGain float64
|
||||
|
||||
// The current pacing rate of the connection.
|
||||
pacingRate Bandwidth
|
||||
|
||||
// The gain currently applied to the pacing rate.
|
||||
pacingGain float64
|
||||
// The gain currently applied to the congestion window.
|
||||
congestionWindowGain float64
|
||||
|
||||
// The gain used for the congestion window during PROBE_BW. Latched from
|
||||
// quic_bbr_cwnd_gain flag.
|
||||
congestionWindowGainConstant float64
|
||||
// The number of RTTs to stay in STARTUP mode. Defaults to 3.
|
||||
numStartupRtts int64
|
||||
|
||||
// Number of round-trips in PROBE_BW mode, used for determining the current
|
||||
// pacing gain cycle.
|
||||
cycleCurrentOffset int
|
||||
// The time at which the last pacing gain cycle was started.
|
||||
lastCycleStart congestion.Time
|
||||
|
||||
// Indicates whether the connection has reached the full bandwidth mode.
|
||||
isAtFullBandwidth bool
|
||||
// Number of rounds during which there was no significant bandwidth increase.
|
||||
roundsWithoutBandwidthGain int64
|
||||
// The bandwidth compared to which the increase is measured.
|
||||
bandwidthAtLastRound Bandwidth
|
||||
|
||||
// Set to true upon exiting quiescence.
|
||||
exitingQuiescence bool
|
||||
|
||||
// Time at which PROBE_RTT has to be exited. Setting it to zero indicates
|
||||
// that the time is yet unknown as the number of packets in flight has not
|
||||
// reached the required value.
|
||||
exitProbeRttAt congestion.Time
|
||||
// Indicates whether a round-trip has passed since PROBE_RTT became active.
|
||||
probeRttRoundPassed bool
|
||||
|
||||
// Indicates whether the most recent bandwidth sample was marked as
|
||||
// app-limited.
|
||||
lastSampleIsAppLimited bool
|
||||
// Indicates whether any non app-limited samples have been recorded.
|
||||
hasNoAppLimitedSample bool
|
||||
|
||||
// Current state of recovery.
|
||||
recoveryState bbrRecoveryState
|
||||
// Receiving acknowledgement of a packet after |end_recovery_at_| will cause
|
||||
// BBR to exit the recovery mode. A value above zero indicates at least one
|
||||
// loss has been detected, so it must not be set back to zero.
|
||||
endRecoveryAt congestion.PacketNumber
|
||||
// A window used to limit the number of bytes in flight during loss recovery.
|
||||
recoveryWindow congestion.ByteCount
|
||||
// If true, consider all samples in recovery app-limited.
|
||||
isAppLimitedRecovery bool // not used
|
||||
|
||||
// When true, pace at 1.5x and disable packet conservation in STARTUP.
|
||||
slowerStartup bool // not used
|
||||
// When true, disables packet conservation in STARTUP.
|
||||
rateBasedStartup bool // not used
|
||||
|
||||
// When true, add the most recent ack aggregation measurement during STARTUP.
|
||||
enableAckAggregationDuringStartup bool
|
||||
// When true, expire the windowed ack aggregation values in STARTUP when
|
||||
// bandwidth increases more than 25%.
|
||||
expireAckAggregationInStartup bool
|
||||
|
||||
// If true, will not exit low gain mode until bytes_in_flight drops below BDP
|
||||
// or it's time for high gain mode.
|
||||
drainToTarget bool
|
||||
|
||||
// If true, slow down pacing rate in STARTUP when overshooting is detected.
|
||||
detectOvershooting bool
|
||||
// Bytes lost while detect_overshooting_ is true.
|
||||
bytesLostWhileDetectingOvershooting congestion.ByteCount
|
||||
// Slow down pacing rate if
|
||||
// bytes_lost_while_detecting_overshooting_ *
|
||||
// bytes_lost_multiplier_while_detecting_overshooting_ > IW.
|
||||
bytesLostMultiplierWhileDetectingOvershooting uint8
|
||||
// When overshooting is detected, do not drop pacing_rate_ below this value /
|
||||
// min_rtt.
|
||||
cwndToCalculateMinPacingRate congestion.ByteCount
|
||||
|
||||
// Max congestion window when adjusting network parameters.
|
||||
maxCongestionWindowWithNetworkParametersAdjusted congestion.ByteCount // not used
|
||||
|
||||
// Params.
|
||||
maxDatagramSize congestion.ByteCount
|
||||
// Recorded on packet sent. equivalent |unacked_packets_->bytes_in_flight()|
|
||||
bytesInFlight congestion.ByteCount
|
||||
|
||||
debug bool
|
||||
}
|
||||
|
||||
var _ congestion.CongestionControl = &bbrSender{}
|
||||
|
||||
func NewBbrSender(
|
||||
clock Clock,
|
||||
initialMaxDatagramSize congestion.ByteCount,
|
||||
) *bbrSender {
|
||||
return newBbrSender(
|
||||
clock,
|
||||
initialMaxDatagramSize,
|
||||
initialCongestionWindowPackets*initialMaxDatagramSize,
|
||||
congestion.MaxCongestionWindowPackets*initialMaxDatagramSize,
|
||||
)
|
||||
}
|
||||
|
||||
func newBbrSender(
|
||||
clock Clock,
|
||||
initialMaxDatagramSize,
|
||||
initialCongestionWindow,
|
||||
initialMaxCongestionWindow congestion.ByteCount,
|
||||
) *bbrSender {
|
||||
debug, _ := strconv.ParseBool(os.Getenv(debugEnv))
|
||||
b := &bbrSender{
|
||||
clock: clock,
|
||||
mode: bbrModeStartup,
|
||||
sampler: newBandwidthSampler(roundTripCount(bandwidthWindowSize)),
|
||||
lastSentPacket: invalidPacketNumber,
|
||||
currentRoundTripEnd: invalidPacketNumber,
|
||||
maxBandwidth: NewWindowedFilter(roundTripCount(bandwidthWindowSize), MaxFilter[Bandwidth]),
|
||||
congestionWindow: initialCongestionWindow,
|
||||
initialCongestionWindow: initialCongestionWindow,
|
||||
maxCongestionWindow: initialMaxCongestionWindow,
|
||||
minCongestionWindow: defaultMinimumCongestionWindow,
|
||||
highGain: defaultHighGain,
|
||||
highCwndGain: defaultHighGain,
|
||||
drainGain: 1.0 / defaultHighGain,
|
||||
pacingGain: 1.0,
|
||||
congestionWindowGain: 1.0,
|
||||
congestionWindowGainConstant: 2.0,
|
||||
numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup,
|
||||
recoveryState: bbrRecoveryStateNotInRecovery,
|
||||
endRecoveryAt: invalidPacketNumber,
|
||||
recoveryWindow: initialMaxCongestionWindow,
|
||||
bytesLostMultiplierWhileDetectingOvershooting: 2,
|
||||
cwndToCalculateMinPacingRate: initialCongestionWindow,
|
||||
maxCongestionWindowWithNetworkParametersAdjusted: initialMaxCongestionWindow,
|
||||
maxDatagramSize: initialMaxDatagramSize,
|
||||
debug: debug,
|
||||
}
|
||||
b.pacer = common.NewPacer(b.bandwidthForPacer)
|
||||
|
||||
/*
|
||||
if b.tracer != nil {
|
||||
b.lastState = logging.CongestionStateStartup
|
||||
b.tracer.UpdatedCongestionState(logging.CongestionStateStartup)
|
||||
}
|
||||
*/
|
||||
|
||||
b.enterStartupMode(b.clock.Now())
|
||||
b.setHighCwndGain(derivedHighCWNDGain)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) {
|
||||
b.rttStats = provider
|
||||
}
|
||||
|
||||
// TimeUntilSend implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) TimeUntilSend(bytesInFlight congestion.ByteCount) congestion.Time {
|
||||
return b.pacer.TimeUntilSend()
|
||||
}
|
||||
|
||||
// HasPacingBudget implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) HasPacingBudget(now congestion.Time) bool {
|
||||
return b.pacer.Budget(now) >= b.maxDatagramSize
|
||||
}
|
||||
|
||||
// OnPacketSent implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) OnPacketSent(
|
||||
sentTime congestion.Time,
|
||||
bytesInFlight congestion.ByteCount,
|
||||
packetNumber congestion.PacketNumber,
|
||||
bytes congestion.ByteCount,
|
||||
isRetransmittable bool,
|
||||
) {
|
||||
b.pacer.SentPacket(sentTime, bytes)
|
||||
|
||||
b.lastSentPacket = packetNumber
|
||||
b.bytesInFlight = bytesInFlight
|
||||
|
||||
if bytesInFlight == 0 {
|
||||
b.exitingQuiescence = true
|
||||
}
|
||||
|
||||
b.sampler.OnPacketSent(sentTime, packetNumber, bytes, bytesInFlight, isRetransmittable)
|
||||
}
|
||||
|
||||
// CanSend implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) CanSend(bytesInFlight congestion.ByteCount) bool {
|
||||
return bytesInFlight < b.GetCongestionWindow()
|
||||
}
|
||||
|
||||
// MaybeExitSlowStart implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) MaybeExitSlowStart() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// OnPacketAcked implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes, priorInFlight congestion.ByteCount, eventTime congestion.Time) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// OnPacketLost implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// OnRetransmissionTimeout implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// SetMaxDatagramSize implements the SendAlgorithm interface.
|
||||
func (b *bbrSender) SetMaxDatagramSize(s congestion.ByteCount) {
|
||||
if s < b.maxDatagramSize {
|
||||
panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", b.maxDatagramSize, s))
|
||||
}
|
||||
cwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow
|
||||
b.maxDatagramSize = s
|
||||
if cwndIsMinCwnd {
|
||||
b.congestionWindow = b.minCongestionWindow
|
||||
}
|
||||
b.pacer.SetMaxDatagramSize(s)
|
||||
}
|
||||
|
||||
// InSlowStart implements the SendAlgorithmWithDebugInfos interface.
|
||||
func (b *bbrSender) InSlowStart() bool {
|
||||
return b.mode == bbrModeStartup
|
||||
}
|
||||
|
||||
// InRecovery implements the SendAlgorithmWithDebugInfos interface.
|
||||
func (b *bbrSender) InRecovery() bool {
|
||||
return b.recoveryState != bbrRecoveryStateNotInRecovery
|
||||
}
|
||||
|
||||
// GetCongestionWindow implements the SendAlgorithmWithDebugInfos interface.
|
||||
func (b *bbrSender) GetCongestionWindow() congestion.ByteCount {
|
||||
if b.mode == bbrModeProbeRtt {
|
||||
return b.probeRttCongestionWindow()
|
||||
}
|
||||
|
||||
if b.InRecovery() {
|
||||
return min(b.congestionWindow, b.recoveryWindow)
|
||||
}
|
||||
|
||||
return b.congestionWindow
|
||||
}
|
||||
|
||||
func (b *bbrSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
func (b *bbrSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime congestion.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) {
|
||||
totalBytesAckedBefore := b.sampler.TotalBytesAcked()
|
||||
totalBytesLostBefore := b.sampler.TotalBytesLost()
|
||||
|
||||
var isRoundStart, minRttExpired bool
|
||||
var excessAcked, bytesLost congestion.ByteCount
|
||||
|
||||
// The send state of the largest packet in acked_packets, unless it is
|
||||
// empty. If acked_packets is empty, it's the send state of the largest
|
||||
// packet in lost_packets.
|
||||
var lastPacketSendState sendTimeState
|
||||
|
||||
b.maybeAppLimited(priorInFlight)
|
||||
|
||||
// Update bytesInFlight
|
||||
b.bytesInFlight = priorInFlight
|
||||
for _, p := range ackedPackets {
|
||||
b.bytesInFlight -= p.BytesAcked
|
||||
}
|
||||
for _, p := range lostPackets {
|
||||
b.bytesInFlight -= p.BytesLost
|
||||
}
|
||||
|
||||
if len(ackedPackets) != 0 {
|
||||
lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber
|
||||
isRoundStart = b.updateRoundTripCounter(lastAckedPacket)
|
||||
b.updateRecoveryState(lastAckedPacket, len(lostPackets) != 0, isRoundStart)
|
||||
}
|
||||
|
||||
sample := b.sampler.OnCongestionEvent(eventTime,
|
||||
ackedPackets, lostPackets, b.maxBandwidth.GetBest(), infBandwidth, b.roundTripCount)
|
||||
if sample.lastPacketSendState.isValid {
|
||||
b.lastSampleIsAppLimited = sample.lastPacketSendState.isAppLimited
|
||||
b.hasNoAppLimitedSample = b.hasNoAppLimitedSample || !b.lastSampleIsAppLimited
|
||||
}
|
||||
// Avoid updating |max_bandwidth_| if a) this is a loss-only event, or b) all
|
||||
// packets in |acked_packets| did not generate valid samples. (e.g. ack of
|
||||
// ack-only packets). In both cases, sampler_.total_bytes_acked() will not
|
||||
// change.
|
||||
if totalBytesAckedBefore != b.sampler.TotalBytesAcked() {
|
||||
if !sample.sampleIsAppLimited || sample.sampleMaxBandwidth > b.maxBandwidth.GetBest() {
|
||||
b.maxBandwidth.Update(sample.sampleMaxBandwidth, b.roundTripCount)
|
||||
}
|
||||
}
|
||||
|
||||
if sample.sampleRtt != infRTT {
|
||||
minRttExpired = b.maybeUpdateMinRtt(eventTime, sample.sampleRtt)
|
||||
}
|
||||
bytesLost = b.sampler.TotalBytesLost() - totalBytesLostBefore
|
||||
|
||||
excessAcked = sample.extraAcked
|
||||
lastPacketSendState = sample.lastPacketSendState
|
||||
|
||||
if len(lostPackets) != 0 {
|
||||
b.numLossEventsInRound++
|
||||
b.bytesLostInRound += bytesLost
|
||||
}
|
||||
|
||||
// Handle logic specific to PROBE_BW mode.
|
||||
if b.mode == bbrModeProbeBw {
|
||||
b.updateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) != 0)
|
||||
}
|
||||
|
||||
// Handle logic specific to STARTUP and DRAIN modes.
|
||||
if isRoundStart && !b.isAtFullBandwidth {
|
||||
b.checkIfFullBandwidthReached(&lastPacketSendState)
|
||||
}
|
||||
|
||||
b.maybeExitStartupOrDrain(eventTime)
|
||||
|
||||
// Handle logic specific to PROBE_RTT.
|
||||
b.maybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired)
|
||||
|
||||
// Calculate number of packets acked and lost.
|
||||
bytesAcked := b.sampler.TotalBytesAcked() - totalBytesAckedBefore
|
||||
|
||||
// After the model is updated, recalculate the pacing rate and congestion
|
||||
// window.
|
||||
b.calculatePacingRate(bytesLost)
|
||||
b.calculateCongestionWindow(bytesAcked, excessAcked)
|
||||
b.calculateRecoveryWindow(bytesAcked, bytesLost)
|
||||
|
||||
// Cleanup internal state.
|
||||
// This is where we clean up obsolete (acked or lost) packets from the bandwidth sampler.
|
||||
// The "least unacked" should actually be FirstOutstanding, but since we are not passing
|
||||
// that through OnCongestionEventEx, we will only do an estimate using acked/lost packets
|
||||
// for now. Because of fast retransmission, they should differ by no more than 2 packets.
|
||||
// (this is controlled by packetThreshold in quic-go's sentPacketHandler)
|
||||
var leastUnacked congestion.PacketNumber
|
||||
if len(ackedPackets) != 0 {
|
||||
leastUnacked = ackedPackets[len(ackedPackets)-1].PacketNumber - 2
|
||||
} else {
|
||||
leastUnacked = lostPackets[len(lostPackets)-1].PacketNumber + 1
|
||||
}
|
||||
b.sampler.RemoveObsoletePackets(leastUnacked)
|
||||
|
||||
if isRoundStart {
|
||||
b.numLossEventsInRound = 0
|
||||
b.bytesLostInRound = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bbrSender) PacingRate() Bandwidth {
|
||||
if b.pacingRate == 0 {
|
||||
return Bandwidth(b.highGain * float64(
|
||||
BandwidthFromDelta(b.initialCongestionWindow, b.getMinRtt())))
|
||||
}
|
||||
|
||||
return b.pacingRate
|
||||
}
|
||||
|
||||
func (b *bbrSender) hasGoodBandwidthEstimateForResumption() bool {
|
||||
return b.hasNonAppLimitedSample()
|
||||
}
|
||||
|
||||
func (b *bbrSender) hasNonAppLimitedSample() bool {
|
||||
return b.hasNoAppLimitedSample
|
||||
}
|
||||
|
||||
// Sets the pacing gain used in STARTUP. Must be greater than 1.
|
||||
func (b *bbrSender) setHighGain(highGain float64) {
|
||||
b.highGain = highGain
|
||||
if b.mode == bbrModeStartup {
|
||||
b.pacingGain = highGain
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the CWND gain used in STARTUP. Must be greater than 1.
|
||||
func (b *bbrSender) setHighCwndGain(highCwndGain float64) {
|
||||
b.highCwndGain = highCwndGain
|
||||
if b.mode == bbrModeStartup {
|
||||
b.congestionWindowGain = highCwndGain
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the gain used in DRAIN. Must be less than 1.
|
||||
func (b *bbrSender) setDrainGain(drainGain float64) {
|
||||
b.drainGain = drainGain
|
||||
}
|
||||
|
||||
// Get the current bandwidth estimate. Note that Bandwidth is in bits per second.
|
||||
func (b *bbrSender) bandwidthEstimate() Bandwidth {
|
||||
return b.maxBandwidth.GetBest()
|
||||
}
|
||||
|
||||
func (b *bbrSender) bandwidthForPacer() congestion.ByteCount {
|
||||
bps := congestion.ByteCount(float64(b.PacingRate()) / float64(BytesPerSecond))
|
||||
if bps < minBps {
|
||||
// We need to make sure that the bandwidth value for pacer is never zero,
|
||||
// otherwise it will go into an edge case where HasPacingBudget = false
|
||||
// but TimeUntilSend is before, causing the quic-go send loop to go crazy and get stuck.
|
||||
return minBps
|
||||
}
|
||||
return bps
|
||||
}
|
||||
|
||||
// Returns the current estimate of the RTT of the connection. Outside of the
|
||||
// edge cases, this is minimum RTT.
|
||||
func (b *bbrSender) getMinRtt() time.Duration {
|
||||
if b.minRtt != 0 {
|
||||
return b.minRtt
|
||||
}
|
||||
// min_rtt could be available if the handshake packet gets neutered then
|
||||
// gets acknowledged. This could only happen for QUIC crypto where we do not
|
||||
// drop keys.
|
||||
minRtt := b.rttStats.MinRTT()
|
||||
if minRtt == 0 {
|
||||
return 100 * time.Millisecond
|
||||
} else {
|
||||
return minRtt
|
||||
}
|
||||
}
|
||||
|
||||
// Computes the target congestion window using the specified gain.
|
||||
func (b *bbrSender) getTargetCongestionWindow(gain float64) congestion.ByteCount {
|
||||
bdp := bdpFromRttAndBandwidth(b.getMinRtt(), b.bandwidthEstimate())
|
||||
congestionWindow := congestion.ByteCount(gain * float64(bdp))
|
||||
|
||||
// BDP estimate will be zero if no bandwidth samples are available yet.
|
||||
if congestionWindow == 0 {
|
||||
congestionWindow = congestion.ByteCount(gain * float64(b.initialCongestionWindow))
|
||||
}
|
||||
|
||||
return max(congestionWindow, b.minCongestionWindow)
|
||||
}
|
||||
|
||||
// The target congestion window during PROBE_RTT.
|
||||
func (b *bbrSender) probeRttCongestionWindow() congestion.ByteCount {
|
||||
return b.minCongestionWindow
|
||||
}
|
||||
|
||||
func (b *bbrSender) maybeUpdateMinRtt(now congestion.Time, sampleMinRtt time.Duration) bool {
|
||||
// Do not expire min_rtt if none was ever available.
|
||||
minRttExpired := b.minRtt != 0 && now.After(b.minRttTimestamp.Add(minRttExpiry))
|
||||
if minRttExpired || sampleMinRtt < b.minRtt || b.minRtt == 0 {
|
||||
b.minRtt = sampleMinRtt
|
||||
b.minRttTimestamp = now
|
||||
}
|
||||
|
||||
return minRttExpired
|
||||
}
|
||||
|
||||
// Enters the STARTUP mode.
|
||||
func (b *bbrSender) enterStartupMode(now congestion.Time) {
|
||||
b.mode = bbrModeStartup
|
||||
// b.maybeTraceStateChange(logging.CongestionStateStartup)
|
||||
b.pacingGain = b.highGain
|
||||
b.congestionWindowGain = b.highCwndGain
|
||||
|
||||
if b.debug {
|
||||
b.debugPrint("Phase: STARTUP")
|
||||
}
|
||||
}
|
||||
|
||||
// Enters the PROBE_BW mode.
|
||||
func (b *bbrSender) enterProbeBandwidthMode(now congestion.Time) {
|
||||
b.mode = bbrModeProbeBw
|
||||
// b.maybeTraceStateChange(logging.CongestionStateProbeBw)
|
||||
b.congestionWindowGain = b.congestionWindowGainConstant
|
||||
|
||||
// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
|
||||
// excluded because in that case increased gain and decreased gain would not
|
||||
// follow each other.
|
||||
b.cycleCurrentOffset = int(rand.Int31n(congestion.PacketsPerConnectionID)) % (gainCycleLength - 1)
|
||||
if b.cycleCurrentOffset >= 1 {
|
||||
b.cycleCurrentOffset += 1
|
||||
}
|
||||
|
||||
b.lastCycleStart = now
|
||||
b.pacingGain = pacingGain[b.cycleCurrentOffset]
|
||||
|
||||
if b.debug {
|
||||
b.debugPrint("Phase: PROBE_BW")
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the round-trip counter if a round-trip has passed. Returns true if
|
||||
// the counter has been advanced.
|
||||
func (b *bbrSender) updateRoundTripCounter(lastAckedPacket congestion.PacketNumber) bool {
|
||||
if b.currentRoundTripEnd == invalidPacketNumber || lastAckedPacket > b.currentRoundTripEnd {
|
||||
b.roundTripCount++
|
||||
b.currentRoundTripEnd = b.lastSentPacket
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Updates the current gain used in PROBE_BW mode.
|
||||
func (b *bbrSender) updateGainCyclePhase(now congestion.Time, priorInFlight congestion.ByteCount, hasLosses bool) {
|
||||
// In most cases, the cycle is advanced after an RTT passes.
|
||||
shouldAdvanceGainCycling := now.After(b.lastCycleStart.Add(b.getMinRtt()))
|
||||
// If the pacing gain is above 1.0, the connection is trying to probe the
|
||||
// bandwidth by increasing the number of bytes in flight to at least
|
||||
// pacing_gain * BDP. Make sure that it actually reaches the target, as long
|
||||
// as there are no losses suggesting that the buffers are not able to hold
|
||||
// that much.
|
||||
if b.pacingGain > 1.0 && !hasLosses && priorInFlight < b.getTargetCongestionWindow(b.pacingGain) {
|
||||
shouldAdvanceGainCycling = false
|
||||
}
|
||||
|
||||
// If pacing gain is below 1.0, the connection is trying to drain the extra
|
||||
// queue which could have been incurred by probing prior to it. If the number
|
||||
// of bytes in flight falls down to the estimated BDP value earlier, conclude
|
||||
// that the queue has been successfully drained and exit this cycle early.
|
||||
if b.pacingGain < 1.0 && b.bytesInFlight <= b.getTargetCongestionWindow(1) {
|
||||
shouldAdvanceGainCycling = true
|
||||
}
|
||||
|
||||
if shouldAdvanceGainCycling {
|
||||
b.cycleCurrentOffset = (b.cycleCurrentOffset + 1) % gainCycleLength
|
||||
b.lastCycleStart = now
|
||||
// Stay in low gain mode until the target BDP is hit.
|
||||
// Low gain mode will be exited immediately when the target BDP is achieved.
|
||||
if b.drainToTarget && b.pacingGain < 1 &&
|
||||
pacingGain[b.cycleCurrentOffset] == 1 &&
|
||||
b.bytesInFlight > b.getTargetCongestionWindow(1) {
|
||||
return
|
||||
}
|
||||
b.pacingGain = pacingGain[b.cycleCurrentOffset]
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks for how many round-trips the bandwidth has not increased
|
||||
// significantly.
|
||||
func (b *bbrSender) checkIfFullBandwidthReached(lastPacketSendState *sendTimeState) {
|
||||
if b.lastSampleIsAppLimited {
|
||||
return
|
||||
}
|
||||
|
||||
target := Bandwidth(float64(b.bandwidthAtLastRound) * startupGrowthTarget)
|
||||
if b.bandwidthEstimate() >= target {
|
||||
b.bandwidthAtLastRound = b.bandwidthEstimate()
|
||||
b.roundsWithoutBandwidthGain = 0
|
||||
if b.expireAckAggregationInStartup {
|
||||
// Expire old excess delivery measurements now that bandwidth increased.
|
||||
b.sampler.ResetMaxAckHeightTracker(0, b.roundTripCount)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
b.roundsWithoutBandwidthGain++
|
||||
if b.roundsWithoutBandwidthGain >= b.numStartupRtts ||
|
||||
b.shouldExitStartupDueToLoss(lastPacketSendState) {
|
||||
b.isAtFullBandwidth = true
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bbrSender) maybeAppLimited(bytesInFlight congestion.ByteCount) {
|
||||
if bytesInFlight < b.getTargetCongestionWindow(1) {
|
||||
b.sampler.OnAppLimited()
|
||||
}
|
||||
}
|
||||
|
||||
// Transitions from STARTUP to DRAIN and from DRAIN to PROBE_BW if
|
||||
// appropriate.
|
||||
func (b *bbrSender) maybeExitStartupOrDrain(now congestion.Time) {
|
||||
if b.mode == bbrModeStartup && b.isAtFullBandwidth {
|
||||
b.mode = bbrModeDrain
|
||||
// b.maybeTraceStateChange(logging.CongestionStateDrain)
|
||||
b.pacingGain = b.drainGain
|
||||
b.congestionWindowGain = b.highCwndGain
|
||||
|
||||
if b.debug {
|
||||
b.debugPrint("Phase: DRAIN")
|
||||
}
|
||||
}
|
||||
if b.mode == bbrModeDrain && b.bytesInFlight <= b.getTargetCongestionWindow(1) {
|
||||
b.enterProbeBandwidthMode(now)
|
||||
}
|
||||
}
|
||||
|
||||
// Decides whether to enter or exit PROBE_RTT.
|
||||
func (b *bbrSender) maybeEnterOrExitProbeRtt(now congestion.Time, isRoundStart, minRttExpired bool) {
|
||||
if minRttExpired && !b.exitingQuiescence && b.mode != bbrModeProbeRtt {
|
||||
b.mode = bbrModeProbeRtt
|
||||
// b.maybeTraceStateChange(logging.CongestionStateProbRtt)
|
||||
b.pacingGain = 1.0
|
||||
// Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight|
|
||||
// is at the target small value.
|
||||
b.exitProbeRttAt = 0
|
||||
|
||||
if b.debug {
|
||||
b.debugPrint("BandwidthEstimate: %s, CongestionWindowGain: %.2f, PacingGain: %.2f, PacingRate: %s",
|
||||
formatSpeed(b.bandwidthEstimate()), b.congestionWindowGain, b.pacingGain, formatSpeed(b.PacingRate()))
|
||||
b.debugPrint("Phase: PROBE_RTT")
|
||||
}
|
||||
}
|
||||
|
||||
if b.mode == bbrModeProbeRtt {
|
||||
b.sampler.OnAppLimited()
|
||||
// b.maybeTraceStateChange(logging.CongestionStateApplicationLimited)
|
||||
|
||||
if b.exitProbeRttAt.IsZero() {
|
||||
// If the window has reached the appropriate size, schedule exiting
|
||||
// PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but
|
||||
// we allow an extra packet since QUIC checks CWND before sending a
|
||||
// packet.
|
||||
if b.bytesInFlight < b.probeRttCongestionWindow()+congestion.MaxPacketBufferSize {
|
||||
b.exitProbeRttAt = now.Add(probeRttTime)
|
||||
b.probeRttRoundPassed = false
|
||||
}
|
||||
} else {
|
||||
if isRoundStart {
|
||||
b.probeRttRoundPassed = true
|
||||
}
|
||||
if now.Sub(b.exitProbeRttAt) >= 0 && b.probeRttRoundPassed {
|
||||
b.minRttTimestamp = now
|
||||
if b.debug {
|
||||
b.debugPrint("MinRTT: %s", b.getMinRtt())
|
||||
}
|
||||
if !b.isAtFullBandwidth {
|
||||
b.enterStartupMode(now)
|
||||
} else {
|
||||
b.enterProbeBandwidthMode(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.exitingQuiescence = false
|
||||
}
|
||||
|
||||
// Determines whether BBR needs to enter, exit or advance state of the
|
||||
// recovery.
|
||||
func (b *bbrSender) updateRecoveryState(lastAckedPacket congestion.PacketNumber, hasLosses, isRoundStart bool) {
|
||||
// Disable recovery in startup, if loss-based exit is enabled.
|
||||
if !b.isAtFullBandwidth {
|
||||
return
|
||||
}
|
||||
|
||||
// Exit recovery when there are no losses for a round.
|
||||
if hasLosses {
|
||||
b.endRecoveryAt = b.lastSentPacket
|
||||
}
|
||||
|
||||
switch b.recoveryState {
|
||||
case bbrRecoveryStateNotInRecovery:
|
||||
if hasLosses {
|
||||
b.recoveryState = bbrRecoveryStateConservation
|
||||
// This will cause the |recovery_window_| to be set to the correct
|
||||
// value in CalculateRecoveryWindow().
|
||||
b.recoveryWindow = 0
|
||||
// Since the conservation phase is meant to be lasting for a whole
|
||||
// round, extend the current round as if it were started right now.
|
||||
b.currentRoundTripEnd = b.lastSentPacket
|
||||
}
|
||||
case bbrRecoveryStateConservation:
|
||||
if isRoundStart {
|
||||
b.recoveryState = bbrRecoveryStateGrowth
|
||||
}
|
||||
fallthrough
|
||||
case bbrRecoveryStateGrowth:
|
||||
// Exit recovery if appropriate.
|
||||
if !hasLosses && lastAckedPacket > b.endRecoveryAt {
|
||||
b.recoveryState = bbrRecoveryStateNotInRecovery
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determines the appropriate pacing rate for the connection.
|
||||
func (b *bbrSender) calculatePacingRate(bytesLost congestion.ByteCount) {
|
||||
if b.bandwidthEstimate() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
targetRate := Bandwidth(b.pacingGain * float64(b.bandwidthEstimate()))
|
||||
if b.isAtFullBandwidth {
|
||||
b.pacingRate = targetRate
|
||||
return
|
||||
}
|
||||
|
||||
// Pace at the rate of initial_window / RTT as soon as RTT measurements are
|
||||
// available.
|
||||
if b.pacingRate == 0 && b.rttStats.MinRTT() != 0 {
|
||||
b.pacingRate = BandwidthFromDelta(b.initialCongestionWindow, b.rttStats.MinRTT())
|
||||
return
|
||||
}
|
||||
|
||||
if b.detectOvershooting {
|
||||
b.bytesLostWhileDetectingOvershooting += bytesLost
|
||||
// Check for overshooting with network parameters adjusted when pacing rate
|
||||
// > target_rate and loss has been detected.
|
||||
if b.pacingRate > targetRate && b.bytesLostWhileDetectingOvershooting > 0 {
|
||||
if b.hasNoAppLimitedSample ||
|
||||
b.bytesLostWhileDetectingOvershooting*congestion.ByteCount(b.bytesLostMultiplierWhileDetectingOvershooting) > b.initialCongestionWindow {
|
||||
// We are fairly sure overshoot happens if 1) there is at least one
|
||||
// non app-limited bw sample or 2) half of IW gets lost. Slow pacing
|
||||
// rate.
|
||||
b.pacingRate = max(targetRate, BandwidthFromDelta(b.cwndToCalculateMinPacingRate, b.rttStats.MinRTT()))
|
||||
b.bytesLostWhileDetectingOvershooting = 0
|
||||
b.detectOvershooting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not decrease the pacing rate during startup.
|
||||
b.pacingRate = max(b.pacingRate, targetRate)
|
||||
}
|
||||
|
||||
// Determines the appropriate congestion window for the connection.
|
||||
func (b *bbrSender) calculateCongestionWindow(bytesAcked, excessAcked congestion.ByteCount) {
|
||||
if b.mode == bbrModeProbeRtt {
|
||||
return
|
||||
}
|
||||
|
||||
targetWindow := b.getTargetCongestionWindow(b.congestionWindowGain)
|
||||
if b.isAtFullBandwidth {
|
||||
// Add the max recently measured ack aggregation to CWND.
|
||||
targetWindow += b.sampler.MaxAckHeight()
|
||||
} else if b.enableAckAggregationDuringStartup {
|
||||
// Add the most recent excess acked. Because CWND never decreases in
|
||||
// STARTUP, this will automatically create a very localized max filter.
|
||||
targetWindow += excessAcked
|
||||
}
|
||||
|
||||
// Instead of immediately setting the target CWND as the new one, BBR grows
|
||||
// the CWND towards |target_window| by only increasing it |bytes_acked| at a
|
||||
// time.
|
||||
if b.isAtFullBandwidth {
|
||||
b.congestionWindow = min(targetWindow, b.congestionWindow+bytesAcked)
|
||||
} else if b.congestionWindow < targetWindow ||
|
||||
b.sampler.TotalBytesAcked() < b.initialCongestionWindow {
|
||||
// If the connection is not yet out of startup phase, do not decrease the
|
||||
// window.
|
||||
b.congestionWindow += bytesAcked
|
||||
}
|
||||
|
||||
// Enforce the limits on the congestion window.
|
||||
b.congestionWindow = max(b.congestionWindow, b.minCongestionWindow)
|
||||
b.congestionWindow = min(b.congestionWindow, b.maxCongestionWindow)
|
||||
}
|
||||
|
||||
// Determines the appropriate window that constrains the in-flight during recovery.
|
||||
func (b *bbrSender) calculateRecoveryWindow(bytesAcked, bytesLost congestion.ByteCount) {
|
||||
if b.recoveryState == bbrRecoveryStateNotInRecovery {
|
||||
return
|
||||
}
|
||||
|
||||
// Set up the initial recovery window.
|
||||
if b.recoveryWindow == 0 {
|
||||
b.recoveryWindow = b.bytesInFlight + bytesAcked
|
||||
b.recoveryWindow = max(b.minCongestionWindow, b.recoveryWindow)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove losses from the recovery window, while accounting for a potential
|
||||
// integer underflow.
|
||||
if b.recoveryWindow >= bytesLost {
|
||||
b.recoveryWindow = b.recoveryWindow - bytesLost
|
||||
} else {
|
||||
b.recoveryWindow = b.maxDatagramSize
|
||||
}
|
||||
|
||||
// In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH,
|
||||
// release additional |bytes_acked| to achieve a slow-start-like behavior.
|
||||
if b.recoveryState == bbrRecoveryStateGrowth {
|
||||
b.recoveryWindow += bytesAcked
|
||||
}
|
||||
|
||||
// Always allow sending at least |bytes_acked| in response.
|
||||
b.recoveryWindow = max(b.recoveryWindow, b.bytesInFlight+bytesAcked)
|
||||
b.recoveryWindow = max(b.minCongestionWindow, b.recoveryWindow)
|
||||
}
|
||||
|
||||
// Return whether we should exit STARTUP due to excessive loss.
|
||||
func (b *bbrSender) shouldExitStartupDueToLoss(lastPacketSendState *sendTimeState) bool {
|
||||
if b.numLossEventsInRound < defaultStartupFullLossCount || !lastPacketSendState.isValid {
|
||||
return false
|
||||
}
|
||||
|
||||
inflightAtSend := lastPacketSendState.bytesInFlight
|
||||
|
||||
if inflightAtSend > 0 && b.bytesLostInRound > 0 {
|
||||
if b.bytesLostInRound > congestion.ByteCount(float64(inflightAtSend)*quicBbr2DefaultLossThreshold) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *bbrSender) debugPrint(format string, a ...any) {
|
||||
fmt.Printf("[BBRSender] [%s] %s\n",
|
||||
time.Now().Format("15:04:05"),
|
||||
fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func bdpFromRttAndBandwidth(rtt time.Duration, bandwidth Bandwidth) congestion.ByteCount {
|
||||
return congestion.ByteCount(rtt) * congestion.ByteCount(bandwidth) / congestion.ByteCount(BytesPerSecond) / congestion.ByteCount(time.Second)
|
||||
}
|
||||
|
||||
func GetInitialPacketSize(addr net.Addr) congestion.ByteCount {
|
||||
// If this is not a UDP address, we don't know anything about the MTU.
|
||||
// Use the minimum size of an Initial packet as the max packet size.
|
||||
if _, ok := addr.(*net.UDPAddr); ok {
|
||||
return congestion.InitialPacketSize
|
||||
} else {
|
||||
return congestion.MinInitialPacketSize
|
||||
}
|
||||
}
|
||||
|
||||
func formatSpeed(bw Bandwidth) string {
|
||||
bwf := float64(bw)
|
||||
units := []string{"bps", "Kbps", "Mbps", "Gbps"}
|
||||
unitIndex := 0
|
||||
for bwf > 1000 && unitIndex < len(units)-1 {
|
||||
bwf /= 1000
|
||||
unitIndex++
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", bwf, units[unitIndex])
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package bbr
|
||||
|
||||
import "github.com/apernet/quic-go/congestion"
|
||||
|
||||
// A Clock returns the current time
|
||||
type Clock interface {
|
||||
Now() congestion.Time
|
||||
}
|
||||
|
||||
// DefaultClock implements the Clock interface using the Go stdlib clock.
|
||||
type DefaultClock struct{}
|
||||
|
||||
var _ Clock = DefaultClock{}
|
||||
|
||||
// Now gets the current time
|
||||
func (DefaultClock) Now() congestion.Time {
|
||||
return congestion.Now()
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package bbr
|
||||
|
||||
import (
|
||||
"github.com/apernet/quic-go/congestion"
|
||||
)
|
||||
|
||||
// packetNumberIndexedQueue is a queue of mostly continuous numbered entries
|
||||
// which supports the following operations:
|
||||
// - adding elements to the end of the queue, or at some point past the end
|
||||
// - removing elements in any order
|
||||
// - retrieving elements
|
||||
// If all elements are inserted in order, all of the operations above are
|
||||
// amortized O(1) time.
|
||||
//
|
||||
// Internally, the data structure is a deque where each element is marked as
|
||||
// present or not. The deque starts at the lowest present index. Whenever an
|
||||
// element is removed, it's marked as not present, and the front of the deque is
|
||||
// cleared of elements that are not present.
|
||||
//
|
||||
// The tail of the queue is not cleared due to the assumption of entries being
|
||||
// inserted in order, though removing all elements of the queue will return it
|
||||
// to its initial state.
|
||||
//
|
||||
// Note that this data structure is inherently hazardous, since an addition of
|
||||
// just two entries will cause it to consume all of the memory available.
|
||||
// Because of that, it is not a general-purpose container and should not be used
|
||||
// as one.
|
||||
|
||||
type entryWrapper[T any] struct {
|
||||
present bool
|
||||
entry T
|
||||
}
|
||||
|
||||
type packetNumberIndexedQueue[T any] struct {
|
||||
entries RingBuffer[entryWrapper[T]]
|
||||
numberOfPresentEntries int
|
||||
firstPacket congestion.PacketNumber
|
||||
}
|
||||
|
||||
func newPacketNumberIndexedQueue[T any](size int) *packetNumberIndexedQueue[T] {
|
||||
q := &packetNumberIndexedQueue[T]{
|
||||
firstPacket: invalidPacketNumber,
|
||||
}
|
||||
|
||||
q.entries.Init(size)
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// Emplace inserts data associated |packet_number| into (or past) the end of the
|
||||
// queue, filling up the missing intermediate entries as necessary. Returns
|
||||
// true if the element has been inserted successfully, false if it was already
|
||||
// in the queue or inserted out of order.
|
||||
func (p *packetNumberIndexedQueue[T]) Emplace(packetNumber congestion.PacketNumber, entry *T) bool {
|
||||
if packetNumber == invalidPacketNumber || entry == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.IsEmpty() {
|
||||
p.entries.PushBack(entryWrapper[T]{
|
||||
present: true,
|
||||
entry: *entry,
|
||||
})
|
||||
p.numberOfPresentEntries = 1
|
||||
p.firstPacket = packetNumber
|
||||
return true
|
||||
}
|
||||
|
||||
// Do not allow insertion out-of-order.
|
||||
if packetNumber <= p.LastPacket() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Handle potentially missing elements.
|
||||
offset := int(packetNumber - p.FirstPacket())
|
||||
if gap := offset - p.entries.Len(); gap > 0 {
|
||||
for i := 0; i < gap; i++ {
|
||||
p.entries.PushBack(entryWrapper[T]{})
|
||||
}
|
||||
}
|
||||
|
||||
p.entries.PushBack(entryWrapper[T]{
|
||||
present: true,
|
||||
entry: *entry,
|
||||
})
|
||||
p.numberOfPresentEntries++
|
||||
return true
|
||||
}
|
||||
|
||||
// GetEntry Retrieve the entry associated with the packet number. Returns the pointer
|
||||
// to the entry in case of success, or nullptr if the entry does not exist.
|
||||
func (p *packetNumberIndexedQueue[T]) GetEntry(packetNumber congestion.PacketNumber) *T {
|
||||
ew := p.getEntryWraper(packetNumber)
|
||||
if ew == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ew.entry
|
||||
}
|
||||
|
||||
// Remove, Same as above, but if an entry is present in the queue, also call f(entry)
|
||||
// before removing it.
|
||||
func (p *packetNumberIndexedQueue[T]) Remove(packetNumber congestion.PacketNumber, f func(T)) bool {
|
||||
ew := p.getEntryWraper(packetNumber)
|
||||
if ew == nil {
|
||||
return false
|
||||
}
|
||||
if f != nil {
|
||||
f(ew.entry)
|
||||
}
|
||||
ew.present = false
|
||||
p.numberOfPresentEntries--
|
||||
|
||||
if packetNumber == p.FirstPacket() {
|
||||
p.clearup()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// RemoveUpTo, but not including |packet_number|.
|
||||
// Unused slots in the front are also removed, which means when the function
|
||||
// returns, |first_packet()| can be larger than |packet_number|.
|
||||
func (p *packetNumberIndexedQueue[T]) RemoveUpTo(packetNumber congestion.PacketNumber) {
|
||||
for !p.entries.Empty() &&
|
||||
p.firstPacket != invalidPacketNumber &&
|
||||
p.firstPacket < packetNumber {
|
||||
if p.entries.Front().present {
|
||||
p.numberOfPresentEntries--
|
||||
}
|
||||
p.entries.PopFront()
|
||||
p.firstPacket++
|
||||
}
|
||||
p.clearup()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty return if queue is empty.
|
||||
func (p *packetNumberIndexedQueue[T]) IsEmpty() bool {
|
||||
return p.numberOfPresentEntries == 0
|
||||
}
|
||||
|
||||
// NumberOfPresentEntries returns the number of entries in the queue.
|
||||
func (p *packetNumberIndexedQueue[T]) NumberOfPresentEntries() int {
|
||||
return p.numberOfPresentEntries
|
||||
}
|
||||
|
||||
// EntrySlotsUsed returns the number of entries allocated in the underlying deque. This is
|
||||
// proportional to the memory usage of the queue.
|
||||
func (p *packetNumberIndexedQueue[T]) EntrySlotsUsed() int {
|
||||
return p.entries.Len()
|
||||
}
|
||||
|
||||
// FirstPacket returns packet number of the first entry in the queue.
|
||||
func (p *packetNumberIndexedQueue[T]) FirstPacket() (packetNumber congestion.PacketNumber) {
|
||||
return p.firstPacket
|
||||
}
|
||||
|
||||
// LastPacket returns packet number of the last entry ever inserted in the queue. Note that the
|
||||
// entry in question may have already been removed. Zero if the queue is
|
||||
// empty.
|
||||
func (p *packetNumberIndexedQueue[T]) LastPacket() (packetNumber congestion.PacketNumber) {
|
||||
if p.IsEmpty() {
|
||||
return invalidPacketNumber
|
||||
}
|
||||
|
||||
return p.firstPacket + congestion.PacketNumber(p.entries.Len()-1)
|
||||
}
|
||||
|
||||
func (p *packetNumberIndexedQueue[T]) clearup() {
|
||||
for !p.entries.Empty() && !p.entries.Front().present {
|
||||
p.entries.PopFront()
|
||||
p.firstPacket++
|
||||
}
|
||||
if p.entries.Empty() {
|
||||
p.firstPacket = invalidPacketNumber
|
||||
}
|
||||
}
|
||||
|
||||
func (p *packetNumberIndexedQueue[T]) getEntryWraper(packetNumber congestion.PacketNumber) *entryWrapper[T] {
|
||||
if packetNumber == invalidPacketNumber ||
|
||||
p.IsEmpty() ||
|
||||
packetNumber < p.firstPacket {
|
||||
return nil
|
||||
}
|
||||
|
||||
offset := int(packetNumber - p.firstPacket)
|
||||
if offset >= p.entries.Len() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ew := p.entries.Offset(offset)
|
||||
if ew == nil || !ew.present {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ew
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user