mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-02 20:08:12 +03:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
07a0dafa41 | ||
|
|
0ca13452b8 |
@@ -95,11 +95,12 @@
|
|||||||
## GUI Clients
|
## GUI Clients
|
||||||
|
|
||||||
- OpenWrt
|
- 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)
|
- [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))
|
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||||
- Asuswrt-Merlin
|
- Asuswrt-Merlin
|
||||||
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
|
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
|
||||||
|
- [fancyss](https://github.com/hq450/fancyss)
|
||||||
- Windows
|
- Windows
|
||||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
- [Furious](https://github.com/LorenEteval/Furious)
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
@@ -112,11 +113,11 @@
|
|||||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- iOS & macOS arm64 & tvOS
|
- 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)
|
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||||
- [OneXray](https://github.com/OneXray/OneXray)
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
- macOS arm64 & x64
|
- 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)
|
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||||
- [Furious](https://github.com/LorenEteval/Furious)
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
|
|||||||
@@ -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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
router "github.com/xtls/xray-core/app/router"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"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/session"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNS is a DNS rely server.
|
// DNS is a DNS rely server.
|
||||||
@@ -100,25 +97,6 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ns := range config.NameServer {
|
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)
|
domainRuleCount += len(ns.PrioritizedDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,76 +580,3 @@ func detectGUIPlatform() bool {
|
|||||||
}
|
}
|
||||||
return false
|
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{
|
ExpectedGeoip: []*router.GeoIP{
|
||||||
{
|
{
|
||||||
// local
|
CountryCode: "local",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*router.CIDR{
|
||||||
{
|
{
|
||||||
// inner ip, will not match
|
// inner ip, will not match
|
||||||
@@ -565,7 +565,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ExpectedGeoip: []*router.GeoIP{
|
ExpectedGeoip: []*router.GeoIP{
|
||||||
{
|
{
|
||||||
// test
|
CountryCode: "test",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*router.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 8},
|
Ip: []byte{8, 8, 8, 8},
|
||||||
@@ -574,7 +574,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// test
|
CountryCode: "test",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*router.CIDR{
|
||||||
{
|
{
|
||||||
Ip: []byte{8, 8, 8, 4},
|
Ip: []byte{8, 8, 8, 4},
|
||||||
@@ -669,7 +669,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ExpectedGeoip: []*router.GeoIP{
|
ExpectedGeoip: []*router.GeoIP{
|
||||||
{ // Will match localhost, localhost-a and localhost-b,
|
{ // Will match localhost, localhost-a and localhost-b,
|
||||||
// local
|
CountryCode: "local",
|
||||||
Cidr: []*router.CIDR{
|
Cidr: []*router.CIDR{
|
||||||
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||||
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||||
|
|||||||
@@ -297,18 +297,3 @@ func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption)
|
|||||||
return 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"
|
"sync"
|
||||||
"time"
|
"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/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sendQuery implements CachedNameserver.
|
// sendQuery implements CachedNameserver.
|
||||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, fqdn string, option dns_feature.IPOption) {
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||||
|
|
||||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
@@ -171,7 +171,14 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, fqdn
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
s.addPendingRequest(udpReq)
|
s.addPendingRequest(udpReq)
|
||||||
b, _ := dns.PackMessage(req.msg)
|
b, err := dns.PackMessage(req.msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||||
b.UDP = ©Dest
|
b.UDP = ©Dest
|
||||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||||
|
|||||||
+83
-33
@@ -2,8 +2,9 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -20,14 +21,23 @@ type Instance struct {
|
|||||||
errorLogger log.Handler
|
errorLogger log.Handler
|
||||||
active bool
|
active bool
|
||||||
dns bool
|
dns bool
|
||||||
|
mask4 int
|
||||||
|
mask6 int
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new log.Instance based on the given config.
|
// New creates a new log.Instance based on the given config.
|
||||||
func New(ctx context.Context, config *Config) (*Instance, error) {
|
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||||
|
m4, m6, err := ParseMaskAddress(config.MaskAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
g := &Instance{
|
g := &Instance{
|
||||||
config: config,
|
config: config,
|
||||||
active: false,
|
active: false,
|
||||||
dns: config.EnableDnsLog,
|
dns: config.EnableDnsLog,
|
||||||
|
mask4: m4,
|
||||||
|
mask6: m6,
|
||||||
}
|
}
|
||||||
log.RegisterHandler(g)
|
log.RegisterHandler(g)
|
||||||
|
|
||||||
@@ -104,7 +114,11 @@ func (g *Instance) Handle(msg log.Message) {
|
|||||||
|
|
||||||
var Msg log.Message
|
var Msg log.Message
|
||||||
if g.config.MaskAddress != "" {
|
if g.config.MaskAddress != "" {
|
||||||
Msg = &MaskedMsgWrapper{Message: msg, config: g.config}
|
Msg = &MaskedMsgWrapper{
|
||||||
|
Message: msg,
|
||||||
|
Mask4: g.mask4,
|
||||||
|
Mask6: g.mask6,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Msg = msg
|
Msg = msg
|
||||||
}
|
}
|
||||||
@@ -149,51 +163,87 @@ func (g *Instance) Close() error {
|
|||||||
return nil
|
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.
|
// MaskedMsgWrapper is to wrap the string() method to mask IP addresses in the log.
|
||||||
type MaskedMsgWrapper struct {
|
type MaskedMsgWrapper struct {
|
||||||
log.Message
|
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 {
|
func (m *MaskedMsgWrapper) String() string {
|
||||||
str := m.Message.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
|
// Process ipv4
|
||||||
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(ip string) string {
|
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(s string) string {
|
||||||
parts := strings.Split(ip, ".")
|
if m.Mask4 == 32 {
|
||||||
switch m.config.MaskAddress {
|
return s
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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
|
// process ipv6
|
||||||
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(ip string) string {
|
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(s string) string {
|
||||||
parts := strings.Split(ip, ":")
|
if m.Mask6 == 128 {
|
||||||
switch m.config.MaskAddress {
|
return s
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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
|
return maskedMsg
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package log_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
@@ -50,3 +51,39 @@ func TestCustomLogHandler(t *testing.T) {
|
|||||||
|
|
||||||
common.Must(logger.Close())
|
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/mux"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"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/core"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"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) {
|
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)
|
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
|
|||||||
return nil, errors.New("unsupported router implementation")
|
return nil, errors.New("unsupported router implementation")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||||
if bo, ok := s.router.(routing.Router); ok {
|
if bo, ok := s.router.(routing.Router); ok {
|
||||||
return &RemoveRuleResponse{}, bo.RemoveRule(request.RuleTag)
|
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")
|
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.
|
// NewRoutingServer creates a statistics service with statistics manager.
|
||||||
func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer {
|
func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer {
|
||||||
return &routingServer{
|
return &routingServer{
|
||||||
|
|||||||
+316
-273
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v5.28.2
|
// protoc v6.33.2
|
||||||
// source: app/router/command/command.proto
|
// source: app/router/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,25 +27,24 @@ const (
|
|||||||
// It conforms to the structure of xray.features.routing.Context and
|
// It conforms to the structure of xray.features.routing.Context and
|
||||||
// xray.features.routing.Route.
|
// xray.features.routing.Route.
|
||||||
type RoutingContext struct {
|
type RoutingContext struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
InboundTag string `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
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"`
|
||||||
InboundTag string `protobuf:"bytes,1,opt,name=InboundTag,proto3" json:"InboundTag,omitempty"`
|
TargetIPs [][]byte `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"`
|
||||||
Network net.Network `protobuf:"varint,2,opt,name=Network,proto3,enum=xray.common.net.Network" json:"Network,omitempty"`
|
SourcePort uint32 `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"`
|
||||||
SourceIPs [][]byte `protobuf:"bytes,3,rep,name=SourceIPs,proto3" json:"SourceIPs,omitempty"`
|
TargetPort uint32 `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"`
|
||||||
TargetIPs [][]byte `protobuf:"bytes,4,rep,name=TargetIPs,proto3" json:"TargetIPs,omitempty"`
|
TargetDomain string `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,omitempty"`
|
||||||
SourcePort uint32 `protobuf:"varint,5,opt,name=SourcePort,proto3" json:"SourcePort,omitempty"`
|
Protocol string `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"`
|
||||||
TargetPort uint32 `protobuf:"varint,6,opt,name=TargetPort,proto3" json:"TargetPort,omitempty"`
|
User string `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"`
|
||||||
TargetDomain string `protobuf:"bytes,7,opt,name=TargetDomain,proto3" json:"TargetDomain,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"`
|
||||||
Protocol string `protobuf:"bytes,8,opt,name=Protocol,proto3" json:"Protocol,omitempty"`
|
OutboundGroupTags []string `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"`
|
||||||
User string `protobuf:"bytes,9,opt,name=User,proto3" json:"User,omitempty"`
|
OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,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"`
|
LocalIPs [][]byte `protobuf:"bytes,13,rep,name=LocalIPs,proto3" json:"LocalIPs,omitempty"`
|
||||||
OutboundGroupTags []string `protobuf:"bytes,11,rep,name=OutboundGroupTags,proto3" json:"OutboundGroupTags,omitempty"`
|
LocalPort uint32 `protobuf:"varint,14,opt,name=LocalPort,proto3" json:"LocalPort,omitempty"`
|
||||||
OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"`
|
VlessRoute uint32 `protobuf:"varint,15,opt,name=VlessRoute,proto3" json:"VlessRoute,omitempty"`
|
||||||
LocalIPs [][]byte `protobuf:"bytes,13,rep,name=LocalIPs,proto3" json:"LocalIPs,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
LocalPort uint32 `protobuf:"varint,14,opt,name=LocalPort,proto3" json:"LocalPort,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
VlessRoute uint32 `protobuf:"varint,15,opt,name=VlessRoute,proto3" json:"VlessRoute,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoutingContext) Reset() {
|
func (x *RoutingContext) Reset() {
|
||||||
@@ -201,11 +201,10 @@ func (x *RoutingContext) GetVlessRoute() uint32 {
|
|||||||
//
|
//
|
||||||
// * If FieldSelectors is left empty, all fields will be returned.
|
// * If FieldSelectors is left empty, all fields will be returned.
|
||||||
type SubscribeRoutingStatsRequest struct {
|
type SubscribeRoutingStatsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
FieldSelectors []string `protobuf:"bytes,1,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SubscribeRoutingStatsRequest) Reset() {
|
func (x *SubscribeRoutingStatsRequest) Reset() {
|
||||||
@@ -253,13 +252,12 @@ func (x *SubscribeRoutingStatsRequest) GetFieldSelectors() []string {
|
|||||||
// * PublishResult broadcasts the routing result to routing statistics channel
|
// * PublishResult broadcasts the routing result to routing statistics channel
|
||||||
// if set true.
|
// if set true.
|
||||||
type TestRouteRequest struct {
|
type TestRouteRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
FieldSelectors []string `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
||||||
|
PublishResult bool `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"`
|
||||||
RoutingContext *RoutingContext `protobuf:"bytes,1,opt,name=RoutingContext,proto3" json:"RoutingContext,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
FieldSelectors []string `protobuf:"bytes,2,rep,name=FieldSelectors,proto3" json:"FieldSelectors,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
PublishResult bool `protobuf:"varint,3,opt,name=PublishResult,proto3" json:"PublishResult,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TestRouteRequest) Reset() {
|
func (x *TestRouteRequest) Reset() {
|
||||||
@@ -314,11 +312,10 @@ func (x *TestRouteRequest) GetPublishResult() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PrincipleTargetInfo struct {
|
type PrincipleTargetInfo struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Tag []string `protobuf:"bytes,1,rep,name=tag,proto3" json:"tag,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Tag []string `protobuf:"bytes,1,rep,name=tag,proto3" json:"tag,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PrincipleTargetInfo) Reset() {
|
func (x *PrincipleTargetInfo) Reset() {
|
||||||
@@ -359,11 +356,10 @@ func (x *PrincipleTargetInfo) GetTag() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OverrideInfo struct {
|
type OverrideInfo struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *OverrideInfo) Reset() {
|
func (x *OverrideInfo) Reset() {
|
||||||
@@ -404,12 +400,11 @@ func (x *OverrideInfo) GetTarget() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BalancerMsg struct {
|
type BalancerMsg struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Override *OverrideInfo `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
PrincipleTarget *PrincipleTargetInfo `protobuf:"bytes,6,opt,name=principle_target,json=principleTarget,proto3" json:"principle_target,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
Override *OverrideInfo `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
PrincipleTarget *PrincipleTargetInfo `protobuf:"bytes,6,opt,name=principle_target,json=principleTarget,proto3" json:"principle_target,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BalancerMsg) Reset() {
|
func (x *BalancerMsg) Reset() {
|
||||||
@@ -457,11 +452,10 @@ func (x *BalancerMsg) GetPrincipleTarget() *PrincipleTargetInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetBalancerInfoRequest struct {
|
type GetBalancerInfoRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBalancerInfoRequest) Reset() {
|
func (x *GetBalancerInfoRequest) Reset() {
|
||||||
@@ -502,11 +496,10 @@ func (x *GetBalancerInfoRequest) GetTag() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetBalancerInfoResponse struct {
|
type GetBalancerInfoResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Balancer *BalancerMsg `protobuf:"bytes,1,opt,name=balancer,proto3" json:"balancer,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Balancer *BalancerMsg `protobuf:"bytes,1,opt,name=balancer,proto3" json:"balancer,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBalancerInfoResponse) Reset() {
|
func (x *GetBalancerInfoResponse) Reset() {
|
||||||
@@ -547,12 +540,11 @@ func (x *GetBalancerInfoResponse) GetBalancer() *BalancerMsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OverrideBalancerTargetRequest struct {
|
type OverrideBalancerTargetRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
BalancerTag string `protobuf:"bytes,1,opt,name=balancerTag,proto3" json:"balancerTag,omitempty"`
|
|
||||||
Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *OverrideBalancerTargetRequest) Reset() {
|
func (x *OverrideBalancerTargetRequest) Reset() {
|
||||||
@@ -600,9 +592,9 @@ func (x *OverrideBalancerTargetRequest) GetTarget() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OverrideBalancerTargetResponse struct {
|
type OverrideBalancerTargetResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *OverrideBalancerTargetResponse) Reset() {
|
func (x *OverrideBalancerTargetResponse) Reset() {
|
||||||
@@ -636,12 +628,11 @@ func (*OverrideBalancerTargetResponse) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AddRuleRequest struct {
|
type AddRuleRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddRuleRequest) Reset() {
|
func (x *AddRuleRequest) Reset() {
|
||||||
@@ -689,9 +680,9 @@ func (x *AddRuleRequest) GetShouldAppend() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AddRuleResponse struct {
|
type AddRuleResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddRuleResponse) Reset() {
|
func (x *AddRuleResponse) Reset() {
|
||||||
@@ -725,11 +716,10 @@ func (*AddRuleResponse) Descriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RemoveRuleRequest struct {
|
type RemoveRuleRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
RuleTag string `protobuf:"bytes,1,opt,name=ruleTag,proto3" json:"ruleTag,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
RuleTag string `protobuf:"bytes,1,opt,name=ruleTag,proto3" json:"ruleTag,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveRuleRequest) Reset() {
|
func (x *RemoveRuleRequest) Reset() {
|
||||||
@@ -770,9 +760,9 @@ func (x *RemoveRuleRequest) GetRuleTag() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RemoveRuleResponse struct {
|
type RemoveRuleResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveRuleResponse) Reset() {
|
func (x *RemoveRuleResponse) Reset() {
|
||||||
@@ -805,15 +795,147 @@ func (*RemoveRuleResponse) Descriptor() ([]byte, []int) {
|
|||||||
return file_app_router_command_command_proto_rawDescGZIP(), []int{13}
|
return file_app_router_command_command_proto_rawDescGZIP(), []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type ListRuleRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
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() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -825,7 +947,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
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 {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -838,187 +960,103 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
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 protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_app_router_command_command_proto_rawDesc = []byte{
|
const file_app_router_command_command_proto_rawDesc = "" +
|
||||||
0x0a, 0x20, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d,
|
"\n" +
|
||||||
0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f,
|
" app/router/command/command.proto\x12\x17xray.app.router.command\x1a\x18common/net/network.proto\x1a!common/serial/typed_message.proto\"\xf6\x04\n" +
|
||||||
0x74, 0x6f, 0x12, 0x17, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
|
"\x0eRoutingContext\x12\x1e\n" +
|
||||||
0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x18, 0x63, 0x6f, 0x6d,
|
"\n" +
|
||||||
0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e,
|
"InboundTag\x18\x01 \x01(\tR\n" +
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65,
|
"InboundTag\x122\n" +
|
||||||
0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
|
"\aNetwork\x18\x02 \x01(\x0e2\x18.xray.common.net.NetworkR\aNetwork\x12\x1c\n" +
|
||||||
0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf6, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75,
|
"\tSourceIPs\x18\x03 \x03(\fR\tSourceIPs\x12\x1c\n" +
|
||||||
0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x49,
|
"\tTargetIPs\x18\x04 \x03(\fR\tTargetIPs\x12\x1e\n" +
|
||||||
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
"\n" +
|
||||||
0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x4e,
|
"SourcePort\x18\x05 \x01(\rR\n" +
|
||||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78,
|
"SourcePort\x12\x1e\n" +
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e,
|
"\n" +
|
||||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12,
|
"TargetPort\x18\x06 \x01(\rR\n" +
|
||||||
0x1c, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x18, 0x03, 0x20, 0x03,
|
"TargetPort\x12\"\n" +
|
||||||
0x28, 0x0c, 0x52, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a,
|
"\fTargetDomain\x18\a \x01(\tR\fTargetDomain\x12\x1a\n" +
|
||||||
0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c,
|
"\bProtocol\x18\b \x01(\tR\bProtocol\x12\x12\n" +
|
||||||
0x52, 0x09, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x50, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x53,
|
"\x04User\x18\t \x01(\tR\x04User\x12W\n" +
|
||||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
"\n" +
|
||||||
0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x54,
|
"Attributes\x18\n" +
|
||||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
" \x03(\v27.xray.app.router.command.RoutingContext.AttributesEntryR\n" +
|
||||||
0x0a, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x54,
|
"Attributes\x12,\n" +
|
||||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,
|
"\x11OutboundGroupTags\x18\v \x03(\tR\x11OutboundGroupTags\x12 \n" +
|
||||||
0x09, 0x52, 0x0c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
"\vOutboundTag\x18\f \x01(\tR\vOutboundTag\x12\x1a\n" +
|
||||||
0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28,
|
"\bLocalIPs\x18\r \x03(\fR\bLocalIPs\x12\x1c\n" +
|
||||||
0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55,
|
"\tLocalPort\x18\x0e \x01(\rR\tLocalPort\x12\x1e\n" +
|
||||||
0x73, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12,
|
"\n" +
|
||||||
0x57, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20,
|
"VlessRoute\x18\x0f \x01(\rR\n" +
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
|
"VlessRoute\x1a=\n" +
|
||||||
0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f,
|
"\x0fAttributesEntry\x12\x10\n" +
|
||||||
0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x41, 0x74, 0x74,
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x41, 0x74,
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"F\n" +
|
||||||
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x4f, 0x75, 0x74, 0x62,
|
"\x1cSubscribeRoutingStatsRequest\x12&\n" +
|
||||||
0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20,
|
"\x0eFieldSelectors\x18\x01 \x03(\tR\x0eFieldSelectors\"\xb1\x01\n" +
|
||||||
0x03, 0x28, 0x09, 0x52, 0x11, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x47, 0x72, 0x6f,
|
"\x10TestRouteRequest\x12O\n" +
|
||||||
0x75, 0x70, 0x54, 0x61, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75,
|
"\x0eRoutingContext\x18\x01 \x01(\v2'.xray.app.router.command.RoutingContextR\x0eRoutingContext\x12&\n" +
|
||||||
0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x75, 0x74,
|
"\x0eFieldSelectors\x18\x02 \x03(\tR\x0eFieldSelectors\x12$\n" +
|
||||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61,
|
"\rPublishResult\x18\x03 \x01(\bR\rPublishResult\"'\n" +
|
||||||
0x6c, 0x49, 0x50, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x4c, 0x6f, 0x63, 0x61,
|
"\x13PrincipleTargetInfo\x12\x10\n" +
|
||||||
0x6c, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f, 0x72,
|
"\x03tag\x18\x01 \x03(\tR\x03tag\"&\n" +
|
||||||
0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f,
|
"\fOverrideInfo\x12\x16\n" +
|
||||||
0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
"\x06target\x18\x02 \x01(\tR\x06target\"\xa9\x01\n" +
|
||||||
0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75,
|
"\vBalancerMsg\x12A\n" +
|
||||||
0x74, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
"\boverride\x18\x05 \x01(\v2%.xray.app.router.command.OverrideInfoR\boverride\x12W\n" +
|
||||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
"\x10principle_target\x18\x06 \x01(\v2,.xray.app.router.command.PrincipleTargetInfoR\x0fprincipleTarget\"*\n" +
|
||||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
"\x16GetBalancerInfoRequest\x12\x10\n" +
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
"\x03tag\x18\x01 \x01(\tR\x03tag\"[\n" +
|
||||||
0x01, 0x22, 0x46, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f,
|
"\x17GetBalancerInfoResponse\x12@\n" +
|
||||||
0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
"\bbalancer\x18\x01 \x01(\v2$.xray.app.router.command.BalancerMsgR\bbalancer\"Y\n" +
|
||||||
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74,
|
"\x1dOverrideBalancerTargetRequest\x12 \n" +
|
||||||
0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64,
|
"\vbalancerTag\x18\x01 \x01(\tR\vbalancerTag\x12\x16\n" +
|
||||||
0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xb1, 0x01, 0x0a, 0x10, 0x54, 0x65,
|
"\x06target\x18\x02 \x01(\tR\x06target\" \n" +
|
||||||
0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f,
|
"\x1eOverrideBalancerTargetResponse\"n\n" +
|
||||||
0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
|
"\x0eAddRuleRequest\x128\n" +
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
|
"\x06config\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\x06config\x12\"\n" +
|
||||||
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
"\fshouldAppend\x18\x02 \x01(\bR\fshouldAppend\"\x11\n" +
|
||||||
0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52,
|
"\x0fAddRuleResponse\"-\n" +
|
||||||
0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12,
|
"\x11RemoveRuleRequest\x12\x18\n" +
|
||||||
0x26, 0x0a, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
|
"\aruleTag\x18\x01 \x01(\tR\aruleTag\"\x14\n" +
|
||||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x65,
|
"\x12RemoveRuleResponse\"\x11\n" +
|
||||||
0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69,
|
"\x0fListRuleRequest\":\n" +
|
||||||
0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
|
"\fListRuleItem\x12\x10\n" +
|
||||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x27, 0x0a,
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12\x18\n" +
|
||||||
0x13, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
|
"\aruleTag\x18\x02 \x01(\tR\aruleTag\"O\n" +
|
||||||
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28,
|
"\x10ListRuleResponse\x12;\n" +
|
||||||
0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x26, 0x0a, 0x0c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
|
"\x05rules\x18\x01 \x03(\v2%.xray.app.router.command.ListRuleItemR\x05rules\"\b\n" +
|
||||||
0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
|
"\x06Config2\xa2\x06\n" +
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xa9,
|
"\x0eRoutingService\x12{\n" +
|
||||||
0x01, 0x0a, 0x0b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x41,
|
"\x15SubscribeRoutingStats\x125.xray.app.router.command.SubscribeRoutingStatsRequest\x1a'.xray.app.router.command.RoutingContext\"\x000\x01\x12a\n" +
|
||||||
0x0a, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
|
"\tTestRoute\x12).xray.app.router.command.TestRouteRequest\x1a'.xray.app.router.command.RoutingContext\"\x00\x12v\n" +
|
||||||
0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
|
"\x0fGetBalancerInfo\x12/.xray.app.router.command.GetBalancerInfoRequest\x1a0.xray.app.router.command.GetBalancerInfoResponse\"\x00\x12\x8b\x01\n" +
|
||||||
0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65, 0x72, 0x72,
|
"\x16OverrideBalancerTarget\x126.xray.app.router.command.OverrideBalancerTargetRequest\x1a7.xray.app.router.command.OverrideBalancerTargetResponse\"\x00\x12^\n" +
|
||||||
0x69, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
"\aAddRule\x12'.xray.app.router.command.AddRuleRequest\x1a(.xray.app.router.command.AddRuleResponse\"\x00\x12g\n" +
|
||||||
0x65, 0x12, 0x57, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x74,
|
"\n" +
|
||||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x78, 0x72,
|
"RemoveRule\x12*.xray.app.router.command.RemoveRuleRequest\x1a+.xray.app.router.command.RemoveRuleResponse\"\x00\x12a\n" +
|
||||||
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f,
|
"\bListRule\x12(.xray.app.router.command.ListRuleRequest\x1a).xray.app.router.command.ListRuleResponse\"\x00Bg\n" +
|
||||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54,
|
"\x1bcom.xray.app.router.commandP\x01Z,github.com/xtls/xray-core/app/router/command\xaa\x02\x17Xray.App.Router.Commandb\x06proto3"
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_app_router_command_command_proto_rawDescOnce sync.Once
|
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 {
|
func file_app_router_command_command_proto_rawDescGZIP() []byte {
|
||||||
file_app_router_command_command_proto_rawDescOnce.Do(func() {
|
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
|
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{
|
var file_app_router_command_command_proto_goTypes = []any{
|
||||||
(*RoutingContext)(nil), // 0: xray.app.router.command.RoutingContext
|
(*RoutingContext)(nil), // 0: xray.app.router.command.RoutingContext
|
||||||
(*SubscribeRoutingStatsRequest)(nil), // 1: xray.app.router.command.SubscribeRoutingStatsRequest
|
(*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
|
(*AddRuleResponse)(nil), // 11: xray.app.router.command.AddRuleResponse
|
||||||
(*RemoveRuleRequest)(nil), // 12: xray.app.router.command.RemoveRuleRequest
|
(*RemoveRuleRequest)(nil), // 12: xray.app.router.command.RemoveRuleRequest
|
||||||
(*RemoveRuleResponse)(nil), // 13: xray.app.router.command.RemoveRuleResponse
|
(*RemoveRuleResponse)(nil), // 13: xray.app.router.command.RemoveRuleResponse
|
||||||
(*Config)(nil), // 14: xray.app.router.command.Config
|
(*ListRuleRequest)(nil), // 14: xray.app.router.command.ListRuleRequest
|
||||||
nil, // 15: xray.app.router.command.RoutingContext.AttributesEntry
|
(*ListRuleItem)(nil), // 15: xray.app.router.command.ListRuleItem
|
||||||
(net.Network)(0), // 16: xray.common.net.Network
|
(*ListRuleResponse)(nil), // 16: xray.app.router.command.ListRuleResponse
|
||||||
(*serial.TypedMessage)(nil), // 17: xray.common.serial.TypedMessage
|
(*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{
|
var file_app_router_command_command_proto_depIdxs = []int32{
|
||||||
16, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network
|
19, // 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
|
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
|
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
|
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
|
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
|
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
|
20, // 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
|
15, // 7: xray.app.router.command.ListRuleResponse.rules:type_name -> xray.app.router.command.ListRuleItem
|
||||||
2, // 8: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest
|
1, // 8: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest
|
||||||
6, // 9: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest
|
2, // 9: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest
|
||||||
8, // 10: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest
|
6, // 10: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest
|
||||||
10, // 11: xray.app.router.command.RoutingService.AddRule:input_type -> xray.app.router.command.AddRuleRequest
|
8, // 11: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest
|
||||||
12, // 12: xray.app.router.command.RoutingService.RemoveRule:input_type -> xray.app.router.command.RemoveRuleRequest
|
10, // 12: xray.app.router.command.RoutingService.AddRule:input_type -> xray.app.router.command.AddRuleRequest
|
||||||
0, // 13: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext
|
12, // 13: xray.app.router.command.RoutingService.RemoveRule:input_type -> xray.app.router.command.RemoveRuleRequest
|
||||||
0, // 14: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext
|
14, // 14: xray.app.router.command.RoutingService.ListRule:input_type -> xray.app.router.command.ListRuleRequest
|
||||||
7, // 15: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse
|
0, // 15: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext
|
||||||
9, // 16: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse
|
0, // 16: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext
|
||||||
11, // 17: xray.app.router.command.RoutingService.AddRule:output_type -> xray.app.router.command.AddRuleResponse
|
7, // 17: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse
|
||||||
13, // 18: xray.app.router.command.RoutingService.RemoveRule:output_type -> xray.app.router.command.RemoveRuleResponse
|
9, // 18: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse
|
||||||
13, // [13:19] is the sub-list for method output_type
|
11, // 19: xray.app.router.command.RoutingService.AddRule:output_type -> xray.app.router.command.AddRuleResponse
|
||||||
7, // [7:13] is the sub-list for method input_type
|
13, // 20: xray.app.router.command.RoutingService.RemoveRule:output_type -> xray.app.router.command.RemoveRuleResponse
|
||||||
7, // [7:7] is the sub-list for extension type_name
|
16, // 21: xray.app.router.command.RoutingService.ListRule:output_type -> xray.app.router.command.ListRuleResponse
|
||||||
7, // [7:7] is the sub-list for extension extendee
|
15, // [15:22] is the sub-list for method output_type
|
||||||
0, // [0:7] is the sub-list for field type_name
|
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() }
|
func init() { file_app_router_command_command_proto_init() }
|
||||||
@@ -1075,9 +1119,9 @@ func file_app_router_command_command_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
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,
|
NumEnums: 0,
|
||||||
NumMessages: 16,
|
NumMessages: 19,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
@@ -1086,7 +1130,6 @@ func file_app_router_command_command_proto_init() {
|
|||||||
MessageInfos: file_app_router_command_command_proto_msgTypes,
|
MessageInfos: file_app_router_command_command_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_app_router_command_command_proto = out.File
|
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_goTypes = nil
|
||||||
file_app_router_command_command_proto_depIdxs = nil
|
file_app_router_command_command_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,17 @@ message RemoveRuleRequest {
|
|||||||
|
|
||||||
message RemoveRuleResponse {}
|
message RemoveRuleResponse {}
|
||||||
|
|
||||||
|
message ListRuleRequest {}
|
||||||
|
|
||||||
|
message ListRuleItem {
|
||||||
|
string tag = 1;
|
||||||
|
string ruleTag = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRuleResponse{
|
||||||
|
repeated ListRuleItem rules = 1;
|
||||||
|
}
|
||||||
|
|
||||||
service RoutingService {
|
service RoutingService {
|
||||||
rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest)
|
rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest)
|
||||||
returns (stream RoutingContext) {}
|
returns (stream RoutingContext) {}
|
||||||
@@ -114,6 +125,8 @@ service RoutingService {
|
|||||||
|
|
||||||
rpc AddRule(AddRuleRequest) returns (AddRuleResponse) {}
|
rpc AddRule(AddRuleRequest) returns (AddRuleResponse) {}
|
||||||
rpc RemoveRule(RemoveRuleRequest) returns (RemoveRuleResponse) {}
|
rpc RemoveRule(RemoveRuleRequest) returns (RemoveRuleResponse) {}
|
||||||
|
|
||||||
|
rpc ListRule(ListRuleRequest) returns (ListRuleResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message Config {}
|
message Config {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
// - protoc v5.28.2
|
// - protoc v6.33.2
|
||||||
// source: app/router/command/command.proto
|
// source: app/router/command/command.proto
|
||||||
|
|
||||||
package command
|
package command
|
||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
RoutingService_OverrideBalancerTarget_FullMethodName = "/xray.app.router.command.RoutingService/OverrideBalancerTarget"
|
RoutingService_OverrideBalancerTarget_FullMethodName = "/xray.app.router.command.RoutingService/OverrideBalancerTarget"
|
||||||
RoutingService_AddRule_FullMethodName = "/xray.app.router.command.RoutingService/AddRule"
|
RoutingService_AddRule_FullMethodName = "/xray.app.router.command.RoutingService/AddRule"
|
||||||
RoutingService_RemoveRule_FullMethodName = "/xray.app.router.command.RoutingService/RemoveRule"
|
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.
|
// 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)
|
OverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error)
|
||||||
AddRule(ctx context.Context, in *AddRuleRequest, opts ...grpc.CallOption) (*AddRuleResponse, error)
|
AddRule(ctx context.Context, in *AddRuleRequest, opts ...grpc.CallOption) (*AddRuleResponse, error)
|
||||||
RemoveRule(ctx context.Context, in *RemoveRuleRequest, opts ...grpc.CallOption) (*RemoveRuleResponse, 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 {
|
type routingServiceClient struct {
|
||||||
@@ -116,6 +118,16 @@ func (c *routingServiceClient) RemoveRule(ctx context.Context, in *RemoveRuleReq
|
|||||||
return out, nil
|
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.
|
// RoutingServiceServer is the server API for RoutingService service.
|
||||||
// All implementations must embed UnimplementedRoutingServiceServer
|
// All implementations must embed UnimplementedRoutingServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -126,6 +138,7 @@ type RoutingServiceServer interface {
|
|||||||
OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error)
|
OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error)
|
||||||
AddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error)
|
AddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error)
|
||||||
RemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error)
|
RemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error)
|
||||||
|
ListRule(context.Context, *ListRuleRequest) (*ListRuleResponse, error)
|
||||||
mustEmbedUnimplementedRoutingServiceServer()
|
mustEmbedUnimplementedRoutingServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,22 +150,25 @@ type RoutingServiceServer interface {
|
|||||||
type UnimplementedRoutingServiceServer struct{}
|
type UnimplementedRoutingServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRoutingStatsRequest, grpc.ServerStreamingServer[RoutingContext]) error {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) mustEmbedUnimplementedRoutingServiceServer() {}
|
||||||
func (UnimplementedRoutingServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedRoutingServiceServer) testEmbeddedByValue() {}
|
||||||
@@ -165,7 +181,7 @@ type UnsafeRoutingServiceServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterRoutingServiceServer(s grpc.ServiceRegistrar, srv RoutingServiceServer) {
|
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
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// 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)
|
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.
|
// RoutingService_ServiceDesc is the grpc.ServiceDesc for RoutingService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -303,6 +337,10 @@ var RoutingService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "RemoveRule",
|
MethodName: "RemoveRule",
|
||||||
Handler: _RoutingService_RemoveRule_Handler,
|
Handler: _RoutingService_RemoveRule_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListRule",
|
||||||
|
Handler: _RoutingService_ListRule_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-43
@@ -309,48 +309,6 @@ func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
|
|||||||
return m.Match(attributes)
|
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 {
|
type ProcessNameMatcher struct {
|
||||||
ProcessNames []string
|
ProcessNames []string
|
||||||
AbsPaths []string
|
AbsPaths []string
|
||||||
@@ -400,6 +358,9 @@ func NewProcessNameMatcher(names []string) *ProcessNameMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||||
|
if len(ctx.GetSourceIPs()) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
srcPort := ctx.GetSourcePort().String()
|
srcPort := ctx.GetSourcePort().String()
|
||||||
srcIP := ctx.GetSourceIPs()[0].String()
|
srcIP := ctx.GetSourceIPs()[0].String()
|
||||||
var network string
|
var network string
|
||||||
@@ -439,4 +400,4 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,40 @@
|
|||||||
package router_test
|
package router_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"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"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"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) {
|
func TestGeoIPMatcher(t *testing.T) {
|
||||||
cidrList := []*router.CIDR{
|
cidrList := []*router.CIDR{
|
||||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||||
@@ -159,11 +182,12 @@ func TestGeoIPReverseMatcher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||||
geo := "geoip:cn"
|
ips, err := loadGeoIP("CN")
|
||||||
geoip, err := loadGeoIP(geo)
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||||
|
Cidr: ips,
|
||||||
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||||
@@ -172,11 +196,12 @@ func TestGeoIPMatcher4CN(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGeoIPMatcher6US(t *testing.T) {
|
func TestGeoIPMatcher6US(t *testing.T) {
|
||||||
geo := "geoip:us"
|
ips, err := loadGeoIP("US")
|
||||||
geoip, err := loadGeoIP(geo)
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||||
|
Cidr: ips,
|
||||||
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
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) {
|
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||||
os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
|
path, err := getAssetPath("geoip.dat")
|
||||||
|
if err != nil {
|
||||||
geoip, err := conf.ToCidrList([]string{geo})
|
return nil, err
|
||||||
|
}
|
||||||
|
geoipBytes, err := filesystem.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
var geoipList router.GeoIPList
|
||||||
geoip, err = router.GetGeoIPList(geoip)
|
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
for _, geoip := range geoipList.Entry {
|
||||||
|
if geoip.CountryCode == country {
|
||||||
|
return geoip.Cidr, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(geoip) == 0 {
|
panic("country not found: " + country)
|
||||||
panic("country not found: " + geo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return geoip[0], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||||
geo := "geoip:cn"
|
ips, err := loadGeoIP("CN")
|
||||||
geoip, err := loadGeoIP(geo)
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||||
|
Cidr: ips,
|
||||||
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@@ -222,11 +250,12 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||||
geo := "geoip:us"
|
ips, err := loadGeoIP("US")
|
||||||
geoip, err := loadGeoIP(geo)
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
|
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||||
|
Cidr: ips,
|
||||||
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
package router_test
|
package router_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/router"
|
|
||||||
. "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"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"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"
|
||||||
"github.com/xtls/xray-core/common/protocol/http"
|
"github.com/xtls/xray-core/common/protocol/http"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
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 {
|
func withBackground() routing.Context {
|
||||||
@@ -302,25 +300,32 @@ func TestRoutingRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGeoSiteDomains(geo string) ([]*Domain, error) {
|
func loadGeoSite(country string) ([]*Domain, error) {
|
||||||
os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
|
path, err := getAssetPath("geosite.dat")
|
||||||
|
if err != nil {
|
||||||
domains, err := conf.ParseDomainRule(geo)
|
return nil, err
|
||||||
|
}
|
||||||
|
geositeBytes, err := filesystem.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
var geositeList GeoSiteList
|
||||||
domains, err = router.GetDomainList(domains)
|
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
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) {
|
func TestChinaSites(t *testing.T) {
|
||||||
domains, err := loadGeoSiteDomains("geosite:cn")
|
domains, err := loadGeoSite("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
acMatcher, err := NewMphMatcherGroup(domains)
|
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) {
|
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||||
domains, err := loadGeoSiteDomains("geosite:cn")
|
domains, err := loadGeoSite("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
matcher, err := NewMphMatcherGroup(domains)
|
matcher, err := NewMphMatcherGroup(domains)
|
||||||
@@ -449,11 +412,11 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
|||||||
var geoips []*GeoIP
|
var geoips []*GeoIP
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := loadGeoIP("geoip:cn")
|
ips, err := loadGeoIP("CN")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &GeoIP{
|
||||||
CountryCode: "CN",
|
CountryCode: "CN",
|
||||||
Cidr: ips.Cidr,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,25 +425,25 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &GeoIP{
|
||||||
CountryCode: "JP",
|
CountryCode: "JP",
|
||||||
Cidr: ips.Cidr,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := loadGeoIP("geoip:ca")
|
ips, err := loadGeoIP("CA")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &GeoIP{
|
||||||
CountryCode: "CA",
|
CountryCode: "CA",
|
||||||
Cidr: ips.Cidr,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, err := loadGeoIP("geoip:us")
|
ips, err := loadGeoIP("US")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
geoips = append(geoips, &GeoIP{
|
geoips = append(geoips, &GeoIP{
|
||||||
CountryCode: "US",
|
CountryCode: "US",
|
||||||
Cidr: ips.Cidr,
|
Cidr: ips,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-100
@@ -3,14 +3,11 @@ package router
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"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/outbound"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
@@ -76,15 +73,7 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.Geoip) > 0 {
|
if len(rr.Geoip) > 0 {
|
||||||
geoip := rr.Geoip
|
cond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,20 +98,11 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rr.Domain) > 0 {
|
if len(rr.Domain) > 0 {
|
||||||
domains := rr.Domain
|
matcher, err := NewMphMatcherGroup(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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
|
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)
|
conds.Add(matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,80 +163,3 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
|
|||||||
return nil, errors.New("unrecognized balancer type")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
sync "sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -181,6 +181,21 @@ func (r *Router) RemoveRule(tag string) error {
|
|||||||
return errors.New("empty tag name!")
|
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) {
|
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {
|
||||||
// SkipDNSResolve is set from DNS module.
|
// SkipDNSResolve is set from DNS module.
|
||||||
// the DOH remote server maybe a domain name,
|
// the DOH remote server maybe a domain name,
|
||||||
|
|||||||
@@ -4,22 +4,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintMigrateFeatureInfo prints a notice of the upcoming feature migration.
|
// PrintNonRemovalDeprecatedFeatureWarning prints a warning of the deprecated feature that won't be removed in the near future.
|
||||||
// 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.
|
|
||||||
// Do not remove this function even there is no reference to it.
|
// Do not remove this function even there is no reference to it.
|
||||||
func PrintMigrateFeatureInfo(sourceFeature string, targetFeature string) {
|
func PrintNonRemovalDeprecatedFeatureWarning(sourceFeature string, targetFeature string) {
|
||||||
LogInfo(context.Background(), "The feature "+sourceFeature+" will be migrated to "+targetFeature+" in the future.")
|
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.
|
// PrintDeprecatedFeatureWarning prints a warning for deprecated and going to be removed feature.
|
||||||
// Do not remove this function even there is no reference to it.
|
// Do not remove this function even there is no reference to it.
|
||||||
func PrintDeprecatedFeatureWarning(feature string, migrateFeature string) {
|
func PrintDeprecatedFeatureWarning(feature string, migrateFeature string) {
|
||||||
if len(migrateFeature) > 0 {
|
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 {
|
} 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
|
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 {
|
func (mpl MemoryPortList) Contains(port Port) bool {
|
||||||
for _, pr := range mpl {
|
for _, pr := range mpl {
|
||||||
if pr.Contains(port) {
|
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
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"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) {
|
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)
|
reader, err := NewFileReader(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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
|
package platform
|
||||||
|
|
||||||
import (
|
import "path/filepath"
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LineSeparator() string {
|
func LineSeparator() string {
|
||||||
return "\r\n"
|
return "\r\n"
|
||||||
@@ -14,7 +12,6 @@ func LineSeparator() string {
|
|||||||
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
||||||
func GetAssetLocation(file string) string {
|
func GetAssetLocation(file string) string {
|
||||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||||
|
|
||||||
return filepath.Join(assetPath, file)
|
return filepath.Join(assetPath, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"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"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package singbridge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
B "github.com/sagernet/sing/common/buf"
|
B "github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
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/buf"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
@@ -29,6 +31,8 @@ type PacketConnWrapper struct {
|
|||||||
cached buf.MultiBuffer
|
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) {
|
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||||
if w.cached != nil {
|
if w.cached != nil {
|
||||||
mb, bb := buf.SplitFirst(w.cached)
|
mb, bb := buf.SplitFirst(w.cached)
|
||||||
@@ -47,10 +51,30 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
|||||||
return ToSocksaddr(destination), nil
|
return ToSocksaddr(destination), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mb, err := w.ReadMultiBuffer()
|
|
||||||
if err != nil {
|
// timeout
|
||||||
return M.Socksaddr{}, err
|
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)
|
nb, bb := buf.SplitFirst(mb)
|
||||||
if bb == nil {
|
if bb == nil {
|
||||||
return M.Socksaddr{}, nil
|
return M.Socksaddr{}, nil
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
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 := blake3.New(8, BaseKey)
|
||||||
h.Write([]byte(inbound.Source.String()))
|
h.Write([]byte(inbound.Source.String()))
|
||||||
copy(globalID[:], h.Sum(nil))
|
copy(globalID[:], h.Sum(nil))
|
||||||
|
|||||||
+5
-1
@@ -64,7 +64,11 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) {
|
|||||||
var files []*ConfigSource
|
var files []*ConfigSource
|
||||||
supported := []string{"json", "yaml", "toml"}
|
supported := []string{"json", "yaml", "toml"}
|
||||||
for _, file := range args {
|
for _, file := range args {
|
||||||
format := GetFormat(file)
|
format := "json"
|
||||||
|
if file != "stdin:" {
|
||||||
|
format = GetFormat(file)
|
||||||
|
}
|
||||||
|
|
||||||
if slices.Contains(supported, format) {
|
if slices.Contains(supported, format) {
|
||||||
files = append(files, &ConfigSource{
|
files = append(files, &ConfigSource{
|
||||||
Name: file,
|
Name: file,
|
||||||
|
|||||||
+3
-3
@@ -17,9 +17,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 25
|
Version_x byte = 26
|
||||||
Version_y byte = 12
|
Version_y byte = 1
|
||||||
Version_z byte = 8
|
Version_z byte = 23
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Router interface {
|
|||||||
PickRoute(ctx Context) (Route, error)
|
PickRoute(ctx Context) (Route, error)
|
||||||
AddRule(config *serial.TypedMessage, shouldAppend bool) error
|
AddRule(config *serial.TypedMessage, shouldAppend bool) error
|
||||||
RemoveRule(tag string) error
|
RemoveRule(tag string) error
|
||||||
|
ListRule() []Route
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route is the routing result of Router feature.
|
// Route is the routing result of Router feature.
|
||||||
@@ -65,6 +66,11 @@ func (DefaultRouter) RemoveRule(tag string) error {
|
|||||||
return common.ErrNoClue
|
return common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRule implements Router.
|
||||||
|
func (DefaultRouter) ListRule() []Route {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Start implements common.Runnable.
|
// Start implements common.Runnable.
|
||||||
func (DefaultRouter) Start() error {
|
func (DefaultRouter) Start() error {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
module github.com/xtls/xray-core
|
module github.com/xtls/xray-core
|
||||||
|
|
||||||
go 1.25
|
go 1.25.6
|
||||||
|
|
||||||
require (
|
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/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
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/pelletier/go-toml v1.9.5
|
||||||
github.com/pires/go-proxyproto v0.8.1
|
github.com/pires/go-proxyproto v0.9.0
|
||||||
github.com/quic-go/quic-go v0.58.0
|
github.com/refraction-networking/utls v1.8.2
|
||||||
github.com/refraction-networking/utls v1.8.1
|
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||||
@@ -21,14 +21,16 @@ require (
|
|||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
|
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.47.0
|
||||||
golang.org/x/net v0.48.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/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
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
google.golang.org/grpc v1.78.0
|
google.golang.org/grpc v1.78.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
|
gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
lukechampine.com/blake3 v1.4.1
|
lukechampine.com/blake3 v1.4.1
|
||||||
)
|
)
|
||||||
@@ -46,11 +48,10 @@ require (
|
|||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
golang.org/x/mod v0.30.0 // indirect
|
golang.org/x/mod v0.31.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -38,22 +40,20 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
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 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
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 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
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.9.0 h1:3Qg3CLxWx4wJOw5uxhTvc0VrgsJeerDbGTvexu4UK1E=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.9.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
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/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||||
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
@@ -95,16 +95,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=
|
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-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.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
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-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-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.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
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-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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
@@ -116,21 +118,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.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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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/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.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.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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
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 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
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-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.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.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -156,8 +158,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
|
|||||||
+17
-2
@@ -80,6 +80,21 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
return errors.New("failed to parse name server: ", string(data))
|
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) {
|
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||||
if c.Address == nil {
|
if c.Address == nil {
|
||||||
return nil, errors.New("NameServer address is not specified.")
|
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
|
var originalRules []*dns.NameServer_OriginalRule
|
||||||
|
|
||||||
for _, rule := range c.Domains {
|
for _, rule := range c.Domains {
|
||||||
parsedDomain, err := ParseDomainRule(rule)
|
parsedDomain, err := parseDomainRule(rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid domain rule: ", rule).Base(err)
|
return nil, errors.New("invalid domain rule: ", rule).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pd := range parsedDomain {
|
for _, pd := range parsedDomain {
|
||||||
domains = append(domains, &dns.NameServer_PriorityDomain{
|
domains = append(domains, &dns.NameServer_PriorityDomain{
|
||||||
Type: dns.ToDomainMatchingType(pd.Type),
|
Type: toDomainMatchingType(pd.Type),
|
||||||
Domain: pd.Value,
|
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
|
||||||
|
}
|
||||||
+116
-45
@@ -203,23 +203,17 @@ func loadFile(file string) ([]byte, error) {
|
|||||||
func loadIP(file, code string) ([]*router.CIDR, error) {
|
func loadIP(file, code string) ([]*router.CIDR, error) {
|
||||||
index := file + ":" + code
|
index := file + ":" + code
|
||||||
if IPCache[index] == nil {
|
if IPCache[index] == nil {
|
||||||
|
bs, err := loadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to load file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
bs = find(bs, []byte(code))
|
||||||
|
if bs == nil {
|
||||||
|
return nil, errors.New("code not found in ", file, ": ", code)
|
||||||
|
}
|
||||||
var geoip router.GeoIP
|
var geoip router.GeoIP
|
||||||
|
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
||||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err)
|
||||||
// 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()
|
defer runtime.GC() // or debug.FreeOSMemory()
|
||||||
return geoip.Cidr, nil // do not cache geoip
|
return geoip.Cidr, nil // do not cache geoip
|
||||||
@@ -231,28 +225,18 @@ func loadIP(file, code string) ([]*router.CIDR, error) {
|
|||||||
func loadSite(file, code string) ([]*router.Domain, error) {
|
func loadSite(file, code string) ([]*router.Domain, error) {
|
||||||
index := file + ":" + code
|
index := file + ":" + code
|
||||||
if SiteCache[index] == nil {
|
if SiteCache[index] == nil {
|
||||||
|
bs, err := loadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to load file: ", file).Base(err)
|
||||||
|
}
|
||||||
|
bs = find(bs, []byte(code))
|
||||||
|
if bs == nil {
|
||||||
|
return nil, errors.New("list not found in ", file, ": ", code)
|
||||||
|
}
|
||||||
var geosite router.GeoSite
|
var geosite router.GeoSite
|
||||||
|
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||||
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
|
return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err)
|
||||||
// pass file:code so can build optimized matcher later
|
|
||||||
domain := router.Domain{Value: file + "_" + code}
|
|
||||||
geosite = router.GeoSite{Domain: []*router.Domain{&domain}}
|
|
||||||
|
|
||||||
} 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("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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer runtime.GC() // or debug.FreeOSMemory()
|
defer runtime.GC() // or debug.FreeOSMemory()
|
||||||
return geosite.Domain, nil // do not cache geosite
|
return geosite.Domain, nil // do not cache geosite
|
||||||
SiteCache[index] = &geosite
|
SiteCache[index] = &geosite
|
||||||
@@ -260,13 +244,105 @@ func loadSite(file, code string) ([]*router.Domain, error) {
|
|||||||
return SiteCache[index].Domain, nil
|
return SiteCache[index].Domain, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
||||||
parts := strings.Split(siteWithAttr, "@")
|
parts := strings.Split(siteWithAttr, "@")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return nil, errors.New("empty site")
|
return nil, errors.New("empty site")
|
||||||
}
|
}
|
||||||
country := strings.ToUpper(parts[0])
|
country := strings.ToUpper(parts[0])
|
||||||
attrs := router.ParseAttrs(parts[1:])
|
attrs := parseAttrs(parts[1:])
|
||||||
domains, err := loadSite(file, country)
|
domains, err := loadSite(file, country)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -276,11 +352,6 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
|
|||||||
return domains, nil
|
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))
|
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if attrs.Match(domain) {
|
if attrs.Match(domain) {
|
||||||
@@ -291,7 +362,7 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
|
|||||||
return filteredDomains, nil
|
return filteredDomains, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseDomainRule(domain string) ([]*router.Domain, error) {
|
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
||||||
if strings.HasPrefix(domain, "geosite:") {
|
if strings.HasPrefix(domain, "geosite:") {
|
||||||
country := strings.ToUpper(domain[8:])
|
country := strings.ToUpper(domain[8:])
|
||||||
domains, err := loadGeositeWithAttr("geosite.dat", country)
|
domains, err := loadGeositeWithAttr("geosite.dat", country)
|
||||||
@@ -489,7 +560,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
|
|
||||||
if rawFieldRule.Domain != nil {
|
if rawFieldRule.Domain != nil {
|
||||||
for _, domain := range *rawFieldRule.Domain {
|
for _, domain := range *rawFieldRule.Domain {
|
||||||
rules, err := ParseDomainRule(domain)
|
rules, err := parseDomainRule(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
||||||
}
|
}
|
||||||
@@ -499,7 +570,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
|
|
||||||
if rawFieldRule.Domains != nil {
|
if rawFieldRule.Domains != nil {
|
||||||
for _, domain := range *rawFieldRule.Domains {
|
for _, domain := range *rawFieldRule.Domains {
|
||||||
rules, err := ParseDomainRule(domain)
|
rules, err := parseDomainRule(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ type ShadowsocksServerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
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) {
|
if C.Contains(shadowaead_2022.List, v.Cipher) {
|
||||||
return buildShadowsocks2022(v)
|
return buildShadowsocks2022(v)
|
||||||
}
|
}
|
||||||
@@ -185,6 +187,8 @@ type ShadowsocksClientConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||||
|
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||||
|
|
||||||
if v.Address != nil {
|
if v.Address != nil {
|
||||||
v.Servers = []*ShadowsocksServerTarget{
|
v.Servers = []*ShadowsocksServerTarget{
|
||||||
{
|
{
|
||||||
@@ -255,7 +259,7 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
|||||||
ss := &protocol.ServerEndpoint{
|
ss := &protocol.ServerEndpoint{
|
||||||
Address: server.Address.Build(),
|
Address: server.Address.Build(),
|
||||||
Port: uint32(server.Port),
|
Port: uint32(server.Port),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Level: uint32(server.Level),
|
Level: uint32(server.Level),
|
||||||
Email: server.Email,
|
Email: server.Email,
|
||||||
Account: serial.ToTypedMessage(account),
|
Account: serial.ToTypedMessage(account),
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"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/httpupgrade"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
"github.com/xtls/xray-core/transport/internet/kcp"
|
"github.com/xtls/xray-core/transport/internet/kcp"
|
||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
@@ -332,6 +334,170 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|||||||
return config, nil
|
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 int64 `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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 c.UdpHop.Interval != 0 && c.UdpHop.Interval < 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.Interval = c.UdpHop.Interval
|
||||||
|
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) {
|
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||||
if len(f) > 0 {
|
if len(f) > 0 {
|
||||||
return filesystem.ReadCert(f)
|
return filesystem.ReadCert(f)
|
||||||
@@ -395,27 +561,26 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
Insecure bool `json:"allowInsecure"`
|
Insecure bool `json:"allowInsecure"`
|
||||||
Certs []*TLSCertConfig `json:"certificates"`
|
Certs []*TLSCertConfig `json:"certificates"`
|
||||||
ServerName string `json:"serverName"`
|
ServerName string `json:"serverName"`
|
||||||
ALPN *StringList `json:"alpn"`
|
ALPN *StringList `json:"alpn"`
|
||||||
EnableSessionResumption bool `json:"enableSessionResumption"`
|
EnableSessionResumption bool `json:"enableSessionResumption"`
|
||||||
DisableSystemRoot bool `json:"disableSystemRoot"`
|
DisableSystemRoot bool `json:"disableSystemRoot"`
|
||||||
MinVersion string `json:"minVersion"`
|
MinVersion string `json:"minVersion"`
|
||||||
MaxVersion string `json:"maxVersion"`
|
MaxVersion string `json:"maxVersion"`
|
||||||
CipherSuites string `json:"cipherSuites"`
|
CipherSuites string `json:"cipherSuites"`
|
||||||
Fingerprint string `json:"fingerprint"`
|
Fingerprint string `json:"fingerprint"`
|
||||||
RejectUnknownSNI bool `json:"rejectUnknownSni"`
|
RejectUnknownSNI bool `json:"rejectUnknownSni"`
|
||||||
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
|
||||||
PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"`
|
CurvePreferences *StringList `json:"curvePreferences"`
|
||||||
CurvePreferences *StringList `json:"curvePreferences"`
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
MasterKeyLog string `json:"masterKeyLog"`
|
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
ECHServerKeys string `json:"echServerKeys"`
|
||||||
ECHServerKeys string `json:"echServerKeys"`
|
ECHConfigList string `json:"echConfigList"`
|
||||||
ECHConfigList string `json:"echConfigList"`
|
ECHForceQuery string `json:"echForceQuery"`
|
||||||
ECHForceQuery string `json:"echForceQuery"`
|
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
||||||
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -431,6 +596,9 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
serverName := c.ServerName
|
serverName := c.ServerName
|
||||||
config.AllowInsecure = c.Insecure
|
config.AllowInsecure = c.Insecure
|
||||||
|
if config.AllowInsecure {
|
||||||
|
errors.PrintDeprecatedFeatureWarning("allowInsecure", "pinnedPeerCertSha256")
|
||||||
|
}
|
||||||
if len(c.ServerName) > 0 {
|
if len(c.ServerName) > 0 {
|
||||||
config.ServerName = serverName
|
config.ServerName = serverName
|
||||||
}
|
}
|
||||||
@@ -458,25 +626,20 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.RejectUnknownSni = c.RejectUnknownSNI
|
config.RejectUnknownSni = c.RejectUnknownSNI
|
||||||
|
|
||||||
if c.PinnedPeerCertificateChainSha256 != nil {
|
if c.PinnedPeerCertSha256 != "" {
|
||||||
config.PinnedPeerCertificateChainSha256 = [][]byte{}
|
config.PinnedPeerCertSha256 = [][]byte{}
|
||||||
for _, v := range *c.PinnedPeerCertificateChainSha256 {
|
// Split by tilde separator
|
||||||
hashValue, err := base64.StdEncoding.DecodeString(v)
|
hashes := strings.Split(c.PinnedPeerCertSha256, "~")
|
||||||
|
for _, v := range hashes {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hashValue, err := hex.DecodeString(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.PinnedPeerCertificateChainSha256 = append(config.PinnedPeerCertificateChainSha256, hashValue)
|
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.PinnedPeerCertificatePublicKeySha256 != nil {
|
|
||||||
config.PinnedPeerCertificatePublicKeySha256 = [][]byte{}
|
|
||||||
for _, v := range *c.PinnedPeerCertificatePublicKeySha256 {
|
|
||||||
hashValue, err := base64.StdEncoding.DecodeString(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.PinnedPeerCertificatePublicKeySha256 = append(config.PinnedPeerCertificatePublicKeySha256, hashValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,18 +903,20 @@ func (p TransportProtocol) Build() (string, error) {
|
|||||||
case "kcp", "mkcp":
|
case "kcp", "mkcp":
|
||||||
return "mkcp", nil
|
return "mkcp", nil
|
||||||
case "grpc":
|
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
|
return "grpc", nil
|
||||||
case "ws", "websocket":
|
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
|
return "websocket", nil
|
||||||
case "httpupgrade":
|
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
|
return "httpupgrade", nil
|
||||||
case "h2", "h3", "http":
|
case "h2", "h3", "http":
|
||||||
return "", errors.PrintRemovedFeatureError("HTTP transport (without header padding, etc.)", "XHTTP stream-one H2 & H3")
|
return "", errors.PrintRemovedFeatureError("HTTP transport (without header padding, etc.)", "XHTTP stream-one H2 & H3")
|
||||||
case "quic":
|
case "quic":
|
||||||
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
||||||
|
case "hysteria":
|
||||||
|
return "hysteria", nil
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Config: unknown transport protocol: ", p)
|
return "", errors.New("Config: unknown transport protocol: ", p)
|
||||||
}
|
}
|
||||||
@@ -934,11 +1099,54 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
}, nil
|
}, 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 {
|
type StreamConfig struct {
|
||||||
Address *Address `json:"address"`
|
Address *Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
Network *TransportProtocol `json:"network"`
|
Network *TransportProtocol `json:"network"`
|
||||||
Security string `json:"security"`
|
Security string `json:"security"`
|
||||||
|
Udpmasks []*FinalMask `json:"udpmasks"`
|
||||||
TLSSettings *TLSConfig `json:"tlsSettings"`
|
TLSSettings *TLSConfig `json:"tlsSettings"`
|
||||||
REALITYSettings *REALITYConfig `json:"realitySettings"`
|
REALITYSettings *REALITYConfig `json:"realitySettings"`
|
||||||
RAWSettings *TCPConfig `json:"rawSettings"`
|
RAWSettings *TCPConfig `json:"rawSettings"`
|
||||||
@@ -949,6 +1157,7 @@ type StreamConfig struct {
|
|||||||
GRPCSettings *GRPCConfig `json:"grpcSettings"`
|
GRPCSettings *GRPCConfig `json:"grpcSettings"`
|
||||||
WSSettings *WebSocketConfig `json:"wsSettings"`
|
WSSettings *WebSocketConfig `json:"wsSettings"`
|
||||||
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
||||||
|
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
||||||
SocketSettings *SocketConfig `json:"sockopt"`
|
SocketSettings *SocketConfig `json:"sockopt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,6 +1177,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
config.ProtocolName = protocol
|
config.ProtocolName = protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(c.Security) {
|
switch strings.ToLower(c.Security) {
|
||||||
case "", "none":
|
case "", "none":
|
||||||
case "tls":
|
case "tls":
|
||||||
@@ -1001,6 +1211,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, errors.New(`Unknown security "` + c.Security + `".`)
|
return nil, errors.New(`Unknown security "` + c.Security + `".`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.RAWSettings != nil {
|
if c.RAWSettings != nil {
|
||||||
c.TCPSettings = c.RAWSettings
|
c.TCPSettings = c.RAWSettings
|
||||||
}
|
}
|
||||||
@@ -1067,6 +1278,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
Settings: serial.ToTypedMessage(hs),
|
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 {
|
if c.SocketSettings != nil {
|
||||||
ss, err := c.SocketSettings.Build()
|
ss, err := c.SocketSettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1074,6 +1295,15 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
config.SocketSettings = ss
|
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
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ type TrojanClientConfig struct {
|
|||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *TrojanClientConfig) Build() (proto.Message, error) {
|
func (c *TrojanClientConfig) Build() (proto.Message, error) {
|
||||||
|
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||||
|
|
||||||
if c.Address != nil {
|
if c.Address != nil {
|
||||||
c.Servers = []*TrojanServerTarget{
|
c.Servers = []*TrojanServerTarget{
|
||||||
{
|
{
|
||||||
@@ -74,7 +76,7 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) {
|
|||||||
config.Server = &protocol.ServerEndpoint{
|
config.Server = &protocol.ServerEndpoint{
|
||||||
Address: rec.Address.Build(),
|
Address: rec.Address.Build(),
|
||||||
Port: uint32(rec.Port),
|
Port: uint32(rec.Port),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Level: uint32(rec.Level),
|
Level: uint32(rec.Level),
|
||||||
Email: rec.Email,
|
Email: rec.Email,
|
||||||
Account: serial.ToTypedMessage(&trojan.Account{
|
Account: serial.ToTypedMessage(&trojan.Account{
|
||||||
@@ -115,6 +117,8 @@ type TrojanServerConfig struct {
|
|||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
func (c *TrojanServerConfig) Build() (proto.Message, error) {
|
||||||
|
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
|
||||||
|
|
||||||
config := &trojan.ServerConfig{
|
config := &trojan.ServerConfig{
|
||||||
Users: make([]*protocol.User, len(c.Clients)),
|
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 := new(inbound.Config)
|
||||||
config.Clients = make([]*protocol.User, len(c.Clients))
|
config.Clients = make([]*protocol.User, len(c.Clients))
|
||||||
switch c.Flow {
|
switch c.Flow {
|
||||||
case vless.None:
|
|
||||||
c.Flow = ""
|
|
||||||
case "", vless.XRV:
|
case "", vless.XRV:
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
|
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 {
|
switch account.Flow {
|
||||||
case "":
|
case "":
|
||||||
account.Flow = c.Flow
|
account.Flow = c.Flow
|
||||||
case vless.None:
|
|
||||||
account.Flow = ""
|
|
||||||
case vless.XRV:
|
case vless.XRV:
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
|
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 {
|
if len(account.Testseed) < 4 {
|
||||||
account.Testseed = c.Testseed
|
account.Testseed = c.Testseed
|
||||||
@@ -280,7 +279,9 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
|||||||
account.Id = u.String()
|
account.Id = u.String()
|
||||||
|
|
||||||
switch account.Flow {
|
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:
|
default:
|
||||||
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
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 {
|
type VMessInboundConfig struct {
|
||||||
Users []json.RawMessage `json:"clients"`
|
Users []json.RawMessage `json:"clients"`
|
||||||
Defaults *VMessDefaultConfig `json:"default"`
|
Defaults *VMessDefaultConfig `json:"default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
||||||
|
errors.PrintNonRemovalDeprecatedFeatureWarning("VMess (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||||
|
|
||||||
config := &inbound.Config{}
|
config := &inbound.Config{}
|
||||||
|
|
||||||
if c.Defaults != nil {
|
if c.Defaults != nil {
|
||||||
@@ -113,6 +115,8 @@ type VMessOutboundConfig struct {
|
|||||||
|
|
||||||
// Build implements Buildable
|
// Build implements Buildable
|
||||||
func (c *VMessOutboundConfig) Build() (proto.Message, error) {
|
func (c *VMessOutboundConfig) Build() (proto.Message, error) {
|
||||||
|
errors.PrintNonRemovalDeprecatedFeatureWarning("VMess (with no Forward Secrecy, etc.)", "VLESS Encryption")
|
||||||
|
|
||||||
config := new(outbound.Config)
|
config := new(outbound.Config)
|
||||||
if c.Address != nil {
|
if c.Address != nil {
|
||||||
c.Receivers = []*VMessOutboundTarget{
|
c.Receivers = []*VMessOutboundTarget{
|
||||||
|
|||||||
+12
-8
@@ -43,6 +43,7 @@ var (
|
|||||||
"vless": func() interface{} { return new(VLessOutboundConfig) },
|
"vless": func() interface{} { return new(VLessOutboundConfig) },
|
||||||
"vmess": func() interface{} { return new(VMessOutboundConfig) },
|
"vmess": func() interface{} { return new(VMessOutboundConfig) },
|
||||||
"trojan": func() interface{} { return new(TrojanClientConfig) },
|
"trojan": func() interface{} { return new(TrojanClientConfig) },
|
||||||
|
"hysteria": func() interface{} { return new(HysteriaClientConfig) },
|
||||||
"dns": func() interface{} { return new(DNSOutboundConfig) },
|
"dns": func() interface{} { return new(DNSOutboundConfig) },
|
||||||
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
|
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
|
||||||
}, "protocol", "settings")
|
}, "protocol", "settings")
|
||||||
@@ -117,20 +118,23 @@ func (m *MuxConfig) Build() (*proxyman.MultiplexingConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InboundDetourConfig struct {
|
type InboundDetourConfig struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
PortList *PortList `json:"port"`
|
PortList *PortList `json:"port"`
|
||||||
ListenOn *Address `json:"listen"`
|
ListenOn *Address `json:"listen"`
|
||||||
Settings *json.RawMessage `json:"settings"`
|
Settings *json.RawMessage `json:"settings"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||||
receiverSettings := &proxyman.ReceiverConfig{}
|
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
|
// Listen on anyip, must set PortList
|
||||||
if c.PortList == nil {
|
if c.PortList == nil {
|
||||||
return nil, errors.New("Listen on AnyIP but no Port(s) set in InboundDetour.")
|
return nil, errors.New("Listen on AnyIP but no Port(s) set in InboundDetour.")
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var CmdAPI = &base.Command{
|
|||||||
cmdInboundUserCount,
|
cmdInboundUserCount,
|
||||||
cmdAddRules,
|
cmdAddRules,
|
||||||
cmdRemoveRules,
|
cmdRemoveRules,
|
||||||
|
cmdListRules,
|
||||||
cmdSourceIpBlock,
|
cmdSourceIpBlock,
|
||||||
cmdOnlineStats,
|
cmdOnlineStats,
|
||||||
cmdOnlineStatsIpList,
|
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)
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/main/commands/base"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmdCertChainHash = &base.Command{
|
|
||||||
UsageLine: "{{.Exec}} certChainHash",
|
|
||||||
Short: "Calculate TLS certificates hash.",
|
|
||||||
Long: `
|
|
||||||
xray tls certChainHash --cert <cert.pem>
|
|
||||||
Calculate TLS certificate chain hash.
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmdCertChainHash.Run = executeCertChainHash // break init loop
|
|
||||||
}
|
|
||||||
|
|
||||||
var input = cmdCertChainHash.Flag.String("cert", "fullchain.pem", "The file path of the certificates chain")
|
|
||||||
|
|
||||||
func executeCertChainHash(cmd *base.Command, args []string) {
|
|
||||||
fs := flag.NewFlagSet("certChainHash", flag.ContinueOnError)
|
|
||||||
if err := fs.Parse(args); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
certContent, err := os.ReadFile(*input)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
certChainHashB64 := tls.CalculatePEMCertChainSHA256Hash(certContent)
|
|
||||||
fmt.Println(certChainHashB64)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdLeafCertHash = &base.Command{
|
||||||
|
UsageLine: "{{.Exec}} tls leafCertHash",
|
||||||
|
Short: "Calculate TLS leaf certificate hash.",
|
||||||
|
Long: `
|
||||||
|
xray tls leafCertHash --cert <cert.pem>
|
||||||
|
Calculate TLS leaf certificate hash.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdLeafCertHash.Run = executeLeafCertHash // break init loop
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = cmdLeafCertHash.Flag.String("cert", "fullchain.pem", "The file path of the leaf certificate")
|
||||||
|
|
||||||
|
func executeLeafCertHash(cmd *base.Command, args []string) {
|
||||||
|
fs := flag.NewFlagSet("leafCertHash", flag.ContinueOnError)
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certContent, err := os.ReadFile(*input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certChainHashB64, err := tls.CalculatePEMLeafCertSHA256Hash(certContent)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("failed to decode cert", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(certChainHashB64)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package tls
|
|||||||
import (
|
import (
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -156,8 +156,14 @@ func printTLSConnDetail(tlsConn *gotls.Conn) {
|
|||||||
|
|
||||||
func showCert() func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func showCert() func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
hash := GenerateCertChainHash(rawCerts)
|
var hash []byte
|
||||||
fmt.Println("Certificate Chain Hash: ", base64.StdEncoding.EncodeToString(hash))
|
for _, asn1Data := range rawCerts {
|
||||||
|
cert, _ := x509.ParseCertificate(asn1Data)
|
||||||
|
if cert.IsCA {
|
||||||
|
hash = GenerateCertHash(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Certificate Leaf Hash: ", hex.EncodeToString(hash))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var CmdTLS = &base.Command{
|
|||||||
Commands: []*base.Command{
|
Commands: []*base.Command{
|
||||||
cmdCert,
|
cmdCert,
|
||||||
cmdPing,
|
cmdPing,
|
||||||
cmdCertChainHash,
|
cmdLeafCertHash,
|
||||||
cmdECH,
|
cmdECH,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
+43
-17
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
c "github.com/xtls/xray-core/common/ctx"
|
c "github.com/xtls/xray-core/common/ctx"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"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/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"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
|
// Handler is managing object that tie together tun interface, ip stack and dispatch connections to the routing
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
config *Config
|
config *Config
|
||||||
stack Stack
|
stack Stack
|
||||||
policyManager policy.Manager
|
policyManager policy.Manager
|
||||||
dispatcher routing.Dispatcher
|
dispatcher routing.Dispatcher
|
||||||
|
tag string
|
||||||
|
sniffingRequest session.SniffingRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionHandler interface with the only method that stack is going to push new connections to
|
// 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 {
|
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
|
||||||
var err 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.ctx = core.ToBackgroundDetachedContext(ctx)
|
||||||
t.policyManager = pm
|
t.policyManager = pm
|
||||||
t.dispatcher = dispatcher
|
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
|
// HandleConnection pass the connection coming from the ip stack to the routing dispatcher
|
||||||
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
|
||||||
sid := session.NewID()
|
// when handling is done with any outcome, always signal back to the incoming connection
|
||||||
ctx := c.ContextWithID(t.ctx, sid)
|
// to close, send completion packets back to the network, and cleanup
|
||||||
errors.LogInfo(ctx, "processing connection from: ", conn.RemoteAddr())
|
defer conn.Close()
|
||||||
|
|
||||||
inbound := session.Inbound{}
|
ctx, cancel := context.WithCancel(t.ctx)
|
||||||
inbound.Name = "tun"
|
defer cancel()
|
||||||
inbound.CanSpliceCopy = 1
|
ctx = c.ContextWithID(ctx, session.NewID())
|
||||||
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
|
||||||
inbound.User = &protocol.MemoryUser{
|
source := net.DestinationFromAddr(conn.RemoteAddr())
|
||||||
Level: t.config.UserLevel,
|
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.ContextWithInbound(ctx, &inbound)
|
||||||
|
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||||
|
SniffingRequest: t.sniffingRequest,
|
||||||
|
})
|
||||||
ctx = session.SubContextFromMuxInbound(ctx)
|
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{
|
link := &transport.Link{
|
||||||
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
|
||||||
Writer: buf.NewWriter(conn),
|
Writer: buf.NewWriter(conn),
|
||||||
}
|
}
|
||||||
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
|
||||||
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
errors.LogError(ctx, errors.New("connection closed").Base(err))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.LogInfo(ctx, "connection completed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network implements proxy.Inbound
|
// Network implements proxy.Inbound
|
||||||
|
|||||||
+78
-24
@@ -6,8 +6,10 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
@@ -100,32 +102,21 @@ func (t *stackGVisor) Start() error {
|
|||||||
})
|
})
|
||||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||||
|
|
||||||
udpForwarder := udp.NewForwarder(ipStack, func(r *udp.ForwarderRequest) {
|
// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support
|
||||||
go func(r *udp.ForwarderRequest) {
|
udpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket)
|
||||||
var wq waiter.Queue
|
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
||||||
var id = r.ID()
|
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)
|
return udpForwarder.HandlePacket(src, dst, data)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
|
||||||
|
|
||||||
t.stack = ipStack
|
t.stack = ipStack
|
||||||
t.endpoint = linkEndpoint
|
t.endpoint = linkEndpoint
|
||||||
@@ -133,6 +124,69 @@ func (t *stackGVisor) Start() error {
|
|||||||
return nil
|
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
|
// Close is called by Handler to shut down the stack
|
||||||
func (t *stackGVisor) Close() error {
|
func (t *stackGVisor) Close() error {
|
||||||
if t.stack == nil {
|
if t.stack == nil {
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
utunControlName = "com.apple.net.utun_control"
|
||||||
|
utunOptIfName = 2
|
||||||
|
sysprotoControl = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type DarwinTun struct {
|
||||||
|
tunFd int
|
||||||
|
name string
|
||||||
|
options TunOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Tun = (*DarwinTun)(nil)
|
||||||
|
var _ GVisorTun = (*DarwinTun)(nil)
|
||||||
|
|
||||||
|
func NewTun(options TunOptions) (Tun, error) {
|
||||||
|
tunFd, name, err := openUTun(options.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DarwinTun{
|
||||||
|
tunFd: tunFd,
|
||||||
|
name: name,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) Start() error {
|
||||||
|
if t.options.MTU > 0 {
|
||||||
|
if err := setMTU(t.name, int(t.options.MTU)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setState(t.name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) Close() error {
|
||||||
|
_ = setState(t.name, false)
|
||||||
|
return unix.Close(t.tunFd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||||
|
return newDarwinEndpoint(t.tunFd, t.options.MTU), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openUTun(name string) (int, string, error) {
|
||||||
|
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)
|
||||||
|
if err != nil {
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlInfo := &unix.CtlInfo{}
|
||||||
|
copy(ctlInfo.Name[:], utunControlName)
|
||||||
|
if err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr := &unix.SockaddrCtl{
|
||||||
|
ID: ctlInfo.Id,
|
||||||
|
Unit: parseUTunUnit(name),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.Connect(fd, sockaddr); err != nil {
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.SetNonblock(fd, true); err != nil {
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tunName, err := unix.GetsockoptString(fd, sysprotoControl, utunOptIfName)
|
||||||
|
if err != nil {
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tunName = strings.TrimRight(tunName, "\x00")
|
||||||
|
if tunName == "" {
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
return -1, "", errors.New("empty utun name")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd, tunName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUTunUnit(name string) uint32 {
|
||||||
|
var unit uint32
|
||||||
|
if _, err := fmt.Sscanf(name, "utun%d", &unit); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return unit + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifreqMTU struct {
|
||||||
|
Name [unix.IFNAMSIZ]byte
|
||||||
|
MTU int32
|
||||||
|
_ [12]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifreqFlags struct {
|
||||||
|
Name [unix.IFNAMSIZ]byte
|
||||||
|
Flags int16
|
||||||
|
_ [14]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMTU(name string, mtu int) error {
|
||||||
|
if mtu <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = unix.Close(fd) }()
|
||||||
|
|
||||||
|
ifr := ifreqMTU{MTU: int32(mtu)}
|
||||||
|
copy(ifr.Name[:], name)
|
||||||
|
return ioctlPtr(fd, unix.SIOCSIFMTU, unsafe.Pointer(&ifr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setState(name string, up bool) error {
|
||||||
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = unix.Close(fd) }()
|
||||||
|
|
||||||
|
ifr := ifreqFlags{}
|
||||||
|
copy(ifr.Name[:], name)
|
||||||
|
|
||||||
|
if err := ioctlPtr(fd, unix.SIOCGIFFLAGS, unsafe.Pointer(&ifr)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if up {
|
||||||
|
ifr.Flags |= unix.IFF_UP
|
||||||
|
} else {
|
||||||
|
ifr.Flags &^= unix.IFF_UP
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioctlPtr(fd, unix.SIOCSIFFLAGS, unsafe.Pointer(&ifr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
||||||
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
const utunHeaderSize = 4
|
||||||
|
|
||||||
|
var ErrUnsupportedNetworkProtocol = errors.New("unsupported ip version")
|
||||||
|
|
||||||
|
// DarwinEndpoint implements GVisor stack.LinkEndpoint
|
||||||
|
var _ stack.LinkEndpoint = (*DarwinEndpoint)(nil)
|
||||||
|
|
||||||
|
type DarwinEndpoint struct {
|
||||||
|
tunFd int
|
||||||
|
mtu uint32
|
||||||
|
dispatcherCancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDarwinEndpoint(tunFd int, mtu uint32) *DarwinEndpoint {
|
||||||
|
return &DarwinEndpoint{
|
||||||
|
tunFd: tunFd,
|
||||||
|
mtu: mtu,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) MTU() uint32 {
|
||||||
|
return e.mtu
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) SetMTU(_ uint32) {
|
||||||
|
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) MaxHeaderLength() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {
|
||||||
|
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||||
|
return stack.CapabilityRXChecksumOffload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) 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 *DarwinEndpoint) IsAttached() bool {
|
||||||
|
return e.dispatcherCancel != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) Wait() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||||
|
return header.ARPHardwareNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) {
|
||||||
|
// tun interface doesn't have link layer header, it will be added by the OS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) Close() {
|
||||||
|
if e.dispatcherCancel != nil {
|
||||||
|
e.dispatcherCancel()
|
||||||
|
e.dispatcherCancel = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) SetOnCloseAction(_ func()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||||
|
var n int
|
||||||
|
for _, packetBuffer := range packetBufferList.AsSlice() {
|
||||||
|
family, err := ipFamilyFromPacket(packetBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return n, &tcpip.ErrAborted{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerBytes [utunHeaderSize]byte
|
||||||
|
binary.BigEndian.PutUint32(headerBytes[:], family)
|
||||||
|
|
||||||
|
writeSlices := append([][]byte{headerBytes[:]}, packetBuffer.AsSlices()...)
|
||||||
|
if _, err := unix.Writev(e.tunFd, writeSlices); err != nil {
|
||||||
|
if errors.Is(err, unix.EAGAIN) {
|
||||||
|
return n, &tcpip.ErrWouldBlock{}
|
||||||
|
}
|
||||||
|
return n, &tcpip.ErrAborted{}
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DarwinEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
|
||||||
|
readSize := int(e.mtu)
|
||||||
|
if readSize <= 0 {
|
||||||
|
readSize = 65535
|
||||||
|
}
|
||||||
|
readSize += utunHeaderSize
|
||||||
|
|
||||||
|
buf := make([]byte, readSize)
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
|
||||||
|
n, err := unix.Read(e.tunFd, buf)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
e.Attach(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n <= utunHeaderSize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
networkProtocol, packet, err := parseUTunPacket(buf[:n])
|
||||||
|
if errors.Is(err, ErrUnsupportedNetworkProtocol) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
e.Attach(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcher.DeliverNetworkPacket(networkProtocol, packet)
|
||||||
|
packet.DecRef()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUTunPacket(packet []byte) (tcpip.NetworkProtocolNumber, *stack.PacketBuffer, error) {
|
||||||
|
if len(packet) <= utunHeaderSize {
|
||||||
|
return 0, nil, errors.New("packet too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
family := binary.BigEndian.Uint32(packet[:utunHeaderSize])
|
||||||
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
|
switch family {
|
||||||
|
case uint32(unix.AF_INET):
|
||||||
|
networkProtocol = header.IPv4ProtocolNumber
|
||||||
|
case uint32(unix.AF_INET6):
|
||||||
|
networkProtocol = header.IPv6ProtocolNumber
|
||||||
|
default:
|
||||||
|
return 0, nil, ErrUnsupportedNetworkProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := packet[utunHeaderSize:]
|
||||||
|
packetBuffer := buffer.MakeWithData(payload)
|
||||||
|
return networkProtocol, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
Payload: packetBuffer,
|
||||||
|
IsForwardedPacket: true,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipFamilyFromPacket(packetBuffer *stack.PacketBuffer) (uint32, error) {
|
||||||
|
for _, slice := range packetBuffer.AsSlices() {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch header.IPVersion(slice) {
|
||||||
|
case header.IPv4Version:
|
||||||
|
return uint32(unix.AF_INET), nil
|
||||||
|
case header.IPv6Version:
|
||||||
|
return uint32(unix.AF_INET6), nil
|
||||||
|
default:
|
||||||
|
return 0, ErrUnsupportedNetworkProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("empty packet")
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !linux && !windows && !android
|
//go:build !linux && !windows && !android && !darwin
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -295,7 +295,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
if newCancel != nil {
|
if newCancel != nil {
|
||||||
newCancel()
|
newCancel()
|
||||||
}
|
}
|
||||||
conn.Close()
|
|
||||||
}, sessionPolicy.Timeouts.ConnectionIdle)
|
}, sessionPolicy.Timeouts.ConnectionIdle)
|
||||||
|
|
||||||
clientReader := link.Reader // .(*pipe.Reader)
|
clientReader := link.Reader // .(*pipe.Reader)
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
|
|||||||
})
|
})
|
||||||
stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
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) {
|
go func(r *udp.ForwarderRequest) {
|
||||||
var (
|
var (
|
||||||
wq waiter.Queue
|
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))
|
handler(net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewUDPConn(&wq, ep))
|
||||||
}(r)
|
}(r)
|
||||||
|
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
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(),
|
"\tSys =", units.ByteSize(m.Sys).String(),
|
||||||
"\tNumGC =", m.NumGC)
|
"\tNumGC =", m.NumGC)
|
||||||
}()
|
}()
|
||||||
payload := make([]byte, payloadSize)
|
singleWrite := func(length int) error {
|
||||||
common.Must2(rand.Read(payload))
|
payload := make([]byte, length)
|
||||||
|
common.Must2(rand.Read(payload))
|
||||||
|
|
||||||
nBytes, err := conn.Write(payload)
|
nBytes, err := conn.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if nBytes != len(payload) {
|
if nBytes != len(payload) {
|
||||||
return errors.New("expect ", len(payload), " written, but actually ", nBytes)
|
return errors.New("expect ", len(payload), " written, but actually ", nBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := readFrom2(conn, timeout, payloadSize)
|
response, err := readFrom2(conn, timeout, length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_ = response
|
_ = response
|
||||||
|
|
||||||
if r := bytes.Compare(response, xor(payload)); r != 0 {
|
if r := bytes.Compare(response, xor(payload)); r != 0 {
|
||||||
return errors.New(r)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestSimpleTLSConnection(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -203,7 +203,7 @@ func TestAutoIssuingCertificate(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -304,7 +304,7 @@ func TestTLSOverKCP(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -400,7 +400,7 @@ func TestTLSOverWebSocket(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -512,7 +512,7 @@ func TestGRPC(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -624,7 +624,7 @@ func TestGRPCMultiMode(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -674,7 +674,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
|
|||||||
defer tcpServer.Close()
|
defer tcpServer.Close()
|
||||||
certificateDer := cert.MustGenerate(nil)
|
certificateDer := cert.MustGenerate(nil)
|
||||||
certificate := tls.ParseCertificate(certificateDer)
|
certificate := tls.ParseCertificate(certificateDer)
|
||||||
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
|
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||||
userID := protocol.NewID(uuid.New())
|
userID := protocol.NewID(uuid.New())
|
||||||
serverPort := tcp.PickPort()
|
serverPort := tcp.PickPort()
|
||||||
serverConfig := &core.Config{
|
serverConfig := &core.Config{
|
||||||
@@ -731,7 +731,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -743,8 +743,8 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
|
|||||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
SecuritySettings: []*serial.TypedMessage{
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&tls.Config{
|
serial.ToTypedMessage(&tls.Config{
|
||||||
AllowInsecure: true,
|
AllowInsecure: true,
|
||||||
PinnedPeerCertificateChainSha256: [][]byte{certHash},
|
PinnedPeerCertSha256: [][]byte{certHash},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -771,7 +771,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
defer tcpServer.Close()
|
defer tcpServer.Close()
|
||||||
certificateDer := cert.MustGenerate(nil)
|
certificateDer := cert.MustGenerate(nil)
|
||||||
certificate := tls.ParseCertificate(certificateDer)
|
certificate := tls.ParseCertificate(certificateDer)
|
||||||
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
|
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||||
certHash[1] += 1
|
certHash[1] += 1
|
||||||
userID := protocol.NewID(uuid.New())
|
userID := protocol.NewID(uuid.New())
|
||||||
serverPort := tcp.PickPort()
|
serverPort := tcp.PickPort()
|
||||||
@@ -829,7 +829,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -841,8 +841,8 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
SecuritySettings: []*serial.TypedMessage{
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&tls.Config{
|
serial.ToTypedMessage(&tls.Config{
|
||||||
AllowInsecure: true,
|
AllowInsecure: true,
|
||||||
PinnedPeerCertificateChainSha256: [][]byte{certHash},
|
PinnedPeerCertSha256: [][]byte{certHash},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -869,7 +869,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
|
|||||||
defer tcpServer.Close()
|
defer tcpServer.Close()
|
||||||
certificateDer := cert.MustGenerate(nil)
|
certificateDer := cert.MustGenerate(nil)
|
||||||
certificate := tls.ParseCertificate(certificateDer)
|
certificate := tls.ParseCertificate(certificateDer)
|
||||||
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
|
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||||
userID := protocol.NewID(uuid.New())
|
userID := protocol.NewID(uuid.New())
|
||||||
serverPort := tcp.PickPort()
|
serverPort := tcp.PickPort()
|
||||||
serverConfig := &core.Config{
|
serverConfig := &core.Config{
|
||||||
@@ -926,7 +926,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -938,9 +938,9 @@ func TestUTLSConnectionPinned(t *testing.T) {
|
|||||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
SecuritySettings: []*serial.TypedMessage{
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&tls.Config{
|
serial.ToTypedMessage(&tls.Config{
|
||||||
Fingerprint: "random",
|
Fingerprint: "random",
|
||||||
AllowInsecure: true,
|
AllowInsecure: true,
|
||||||
PinnedPeerCertificateChainSha256: [][]byte{certHash},
|
PinnedPeerCertSha256: [][]byte{certHash},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -967,7 +967,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
defer tcpServer.Close()
|
defer tcpServer.Close()
|
||||||
certificateDer := cert.MustGenerate(nil)
|
certificateDer := cert.MustGenerate(nil)
|
||||||
certificate := tls.ParseCertificate(certificateDer)
|
certificate := tls.ParseCertificate(certificateDer)
|
||||||
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
|
certHash := tls.GenerateCertHash(certificateDer.Certificate)
|
||||||
certHash[1] += 1
|
certHash[1] += 1
|
||||||
userID := protocol.NewID(uuid.New())
|
userID := protocol.NewID(uuid.New())
|
||||||
serverPort := tcp.PickPort()
|
serverPort := tcp.PickPort()
|
||||||
@@ -1025,7 +1025,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
Receiver: &protocol.ServerEndpoint{
|
Receiver: &protocol.ServerEndpoint{
|
||||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||||
Port: uint32(serverPort),
|
Port: uint32(serverPort),
|
||||||
User: &protocol.User{
|
User: &protocol.User{
|
||||||
Account: serial.ToTypedMessage(&vmess.Account{
|
Account: serial.ToTypedMessage(&vmess.Account{
|
||||||
Id: userID.String(),
|
Id: userID.String(),
|
||||||
}),
|
}),
|
||||||
@@ -1037,9 +1037,9 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
|
|||||||
SecurityType: serial.GetMessageType(&tls.Config{}),
|
SecurityType: serial.GetMessageType(&tls.Config{}),
|
||||||
SecuritySettings: []*serial.TypedMessage{
|
SecuritySettings: []*serial.TypedMessage{
|
||||||
serial.ToTypedMessage(&tls.Config{
|
serial.ToTypedMessage(&tls.Config{
|
||||||
Fingerprint: "random",
|
Fingerprint: "random",
|
||||||
AllowInsecure: true,
|
AllowInsecure: true,
|
||||||
PinnedPeerCertificateChainSha256: [][]byte{certHash},
|
PinnedPeerCertSha256: [][]byte{certHash},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (c *StreamConfig) GetEffectiveSecuritySettings() (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *StreamConfig) HasSecuritySettings() bool {
|
func (c *StreamConfig) HasSecuritySettings() bool {
|
||||||
return len(c.SecurityType) > 0
|
return len(c.SecuritySettings) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyConfig) HasTag() bool {
|
func (c *ProxyConfig) HasTag() bool {
|
||||||
@@ -130,7 +130,7 @@ func (s DomainStrategy) FallbackIP6() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s DomainStrategy) GetDynamicStrategy(addrFamily net.AddressFamily) DomainStrategy {
|
func (s DomainStrategy) GetDynamicStrategy(addrFamily net.AddressFamily) DomainStrategy {
|
||||||
if addrFamily.IsDomain(){
|
if addrFamily.IsDomain() {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
switch s {
|
switch s {
|
||||||
|
|||||||
+158
-224
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.10
|
||||||
// protoc v5.28.2
|
// protoc v6.33.1
|
||||||
// source: transport/internet/config.proto
|
// source: transport/internet/config.proto
|
||||||
|
|
||||||
package internet
|
package internet
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -209,14 +210,13 @@ func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransportConfig struct {
|
type TransportConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Transport protocol name.
|
// Transport protocol name.
|
||||||
ProtocolName string `protobuf:"bytes,3,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"`
|
ProtocolName string `protobuf:"bytes,3,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"`
|
||||||
// Specific transport protocol settings.
|
// 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() {
|
func (x *TransportConfig) Reset() {
|
||||||
@@ -264,12 +264,9 @@ func (x *TransportConfig) GetSettings() *serial.TypedMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamConfig struct {
|
type StreamConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Address *net.IPOrDomain `protobuf:"bytes,8,opt,name=address,proto3" json:"address,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
Port uint32 `protobuf:"varint,9,opt,name=port,proto3" json:"port,omitempty"`
|
||||||
|
|
||||||
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.
|
// Effective network.
|
||||||
ProtocolName string `protobuf:"bytes,5,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"`
|
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"`
|
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"`
|
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.
|
// 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"`
|
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"`
|
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() {
|
func (x *StreamConfig) Reset() {
|
||||||
@@ -352,6 +353,20 @@ func (x *StreamConfig) GetSecuritySettings() []*serial.TypedMessage {
|
|||||||
return nil
|
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 {
|
func (x *StreamConfig) GetSocketSettings() *SocketConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.SocketSettings
|
return x.SocketSettings
|
||||||
@@ -360,12 +375,11 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConfig struct {
|
type ProxyConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
TransportLayerProxy bool `protobuf:"varint,2,opt,name=transportLayerProxy,proto3" json:"transportLayerProxy,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProxyConfig) Reset() {
|
func (x *ProxyConfig) Reset() {
|
||||||
@@ -413,16 +427,15 @@ func (x *ProxyConfig) GetTransportLayerProxy() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CustomSockopt struct {
|
type CustomSockopt struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CustomSockopt) Reset() {
|
func (x *CustomSockopt) Reset() {
|
||||||
@@ -499,10 +512,7 @@ func (x *CustomSockopt) GetType() string {
|
|||||||
|
|
||||||
// SocketConfig is options to be applied on network sockets.
|
// SocketConfig is options to be applied on network sockets.
|
||||||
type SocketConfig struct {
|
type SocketConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Mark of the connection. If non-zero, the value will be set to SO_MARK.
|
// 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"`
|
Mark int32 `protobuf:"varint,1,opt,name=mark,proto3" json:"mark,omitempty"`
|
||||||
// TFO is the state of TFO settings.
|
// 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"`
|
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"`
|
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"`
|
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() {
|
func (x *SocketConfig) Reset() {
|
||||||
@@ -725,14 +737,13 @@ func (x *SocketConfig) GetTrustedXForwardedFor() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HappyEyeballsConfig struct {
|
type HappyEyeballsConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
PrioritizeIpv6 bool `protobuf:"varint,1,opt,name=prioritize_ipv6,json=prioritizeIpv6,proto3" json:"prioritize_ipv6,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
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"`
|
||||||
PrioritizeIpv6 bool `protobuf:"varint,1,opt,name=prioritize_ipv6,json=prioritizeIpv6,proto3" json:"prioritize_ipv6,omitempty"`
|
MaxConcurrentTry uint32 `protobuf:"varint,4,opt,name=max_concurrent_try,json=maxConcurrentTry,proto3" json:"max_concurrent_try,omitempty"`
|
||||||
Interleave uint32 `protobuf:"varint,2,opt,name=interleave,proto3" json:"interleave,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
TryDelayMs uint64 `protobuf:"varint,3,opt,name=try_delayMs,json=tryDelayMs,proto3" json:"try_delayMs,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
MaxConcurrentTry uint32 `protobuf:"varint,4,opt,name=max_concurrent_try,json=maxConcurrentTry,proto3" json:"max_concurrent_try,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *HappyEyeballsConfig) Reset() {
|
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 protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_config_proto_rawDesc = []byte{
|
const file_transport_internet_config_proto_rawDesc = "" +
|
||||||
0x0a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
"\n" +
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
"\x1ftransport/internet/config.proto\x12\x17xray.transport.internet\x1a!common/serial/typed_message.proto\x1a\x18common/net/address.proto\"t\n" +
|
||||||
0x6f, 0x12, 0x17, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
"\x0fTransportConfig\x12#\n" +
|
||||||
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d,
|
"\rprotocol_name\x18\x03 \x01(\tR\fprotocolName\x12<\n" +
|
||||||
0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f,
|
"\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\x97\x04\n" +
|
||||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63,
|
"\fStreamConfig\x125\n" +
|
||||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
"\aaddress\x18\b \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
|
||||||
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
"\x04port\x18\t \x01(\rR\x04port\x12#\n" +
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72,
|
"\rprotocol_name\x18\x05 \x01(\tR\fprotocolName\x12W\n" +
|
||||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
"\x12transport_settings\x18\x02 \x03(\v2(.xray.transport.internet.TransportConfigR\x11transportSettings\x12#\n" +
|
||||||
0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12,
|
"\rsecurity_type\x18\x03 \x01(\tR\fsecurityType\x12M\n" +
|
||||||
0x3c, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
"\x11security_settings\x18\x04 \x03(\v2 .xray.common.serial.TypedMessageR\x10securitySettings\x12<\n" +
|
||||||
0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
"\budpmasks\x18\n" +
|
||||||
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
" \x03(\v2 .xray.common.serial.TypedMessageR\budpmasks\x12<\n" +
|
||||||
0x61, 0x67, 0x65, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x9b, 0x03,
|
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12N\n" +
|
||||||
0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35,
|
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"Q\n" +
|
||||||
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
"\vProxyConfig\x12\x10\n" +
|
||||||
0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
|
||||||
0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64,
|
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
|
||||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20,
|
"\rCustomSockopt\x12\x16\n" +
|
||||||
0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f,
|
"\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" +
|
||||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
"\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" +
|
||||||
0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57,
|
"\x05level\x18\x03 \x01(\tR\x05level\x12\x10\n" +
|
||||||
0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74,
|
"\x03opt\x18\x04 \x01(\tR\x03opt\x12\x14\n" +
|
||||||
0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61,
|
"\x05value\x18\x05 \x01(\tR\x05value\x12\x12\n" +
|
||||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
"\x04type\x18\x06 \x01(\tR\x04type\"\x89\t\n" +
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f,
|
"\fSocketConfig\x12\x12\n" +
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53,
|
"\x04mark\x18\x01 \x01(\x05R\x04mark\x12\x10\n" +
|
||||||
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x63, 0x75, 0x72,
|
"\x03tfo\x18\x02 \x01(\x05R\x03tfo\x12H\n" +
|
||||||
0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
"\x06tproxy\x18\x03 \x01(\x0e20.xray.transport.internet.SocketConfig.TProxyModeR\x06tproxy\x12A\n" +
|
||||||
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4d, 0x0a, 0x11,
|
"\x1dreceive_original_dest_address\x18\x04 \x01(\bR\x1areceiveOriginalDestAddress\x12!\n" +
|
||||||
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
"\fbind_address\x18\x05 \x01(\fR\vbindAddress\x12\x1b\n" +
|
||||||
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
|
"\tbind_port\x18\x06 \x01(\rR\bbindPort\x122\n" +
|
||||||
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70,
|
"\x15accept_proxy_protocol\x18\a \x01(\bR\x13acceptProxyProtocol\x12P\n" +
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72,
|
"\x0fdomain_strategy\x18\b \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12!\n" +
|
||||||
0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4e, 0x0a, 0x0f, 0x73,
|
"\fdialer_proxy\x18\t \x01(\tR\vdialerProxy\x125\n" +
|
||||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06,
|
"\x17tcp_keep_alive_interval\x18\n" +
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
" \x01(\x05R\x14tcpKeepAliveInterval\x12-\n" +
|
||||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53,
|
"\x13tcp_keep_alive_idle\x18\v \x01(\x05R\x10tcpKeepAliveIdle\x12%\n" +
|
||||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x6f, 0x63,
|
"\x0etcp_congestion\x18\f \x01(\tR\rtcpCongestion\x12\x1c\n" +
|
||||||
0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x51, 0x0a, 0x0b, 0x50,
|
"\tinterface\x18\r \x01(\tR\tinterface\x12\x16\n" +
|
||||||
0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61,
|
"\x06v6only\x18\x0e \x01(\bR\x06v6only\x12(\n" +
|
||||||
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x13,
|
"\x10tcp_window_clamp\x18\x0f \x01(\x05R\x0etcpWindowClamp\x12(\n" +
|
||||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72,
|
"\x10tcp_user_timeout\x18\x10 \x01(\x05R\x0etcpUserTimeout\x12\x1e\n" +
|
||||||
0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
"\vtcp_max_seg\x18\x11 \x01(\x05R\ttcpMaxSeg\x12\x1c\n" +
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x93,
|
"\tpenetrate\x18\x12 \x01(\bR\tpenetrate\x12\x1b\n" +
|
||||||
0x01, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74,
|
"\ttcp_mptcp\x18\x13 \x01(\bR\btcpMptcp\x12L\n" +
|
||||||
0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
"\rcustomSockopt\x18\x14 \x03(\v2&.xray.transport.internet.CustomSockoptR\rcustomSockopt\x12`\n" +
|
||||||
0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77,
|
"\x15address_port_strategy\x18\x15 \x01(\x0e2,.xray.transport.internet.AddressPortStrategyR\x13addressPortStrategy\x12S\n" +
|
||||||
0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
|
"\x0ehappy_eyeballs\x18\x16 \x01(\v2,.xray.transport.internet.HappyEyeballsConfigR\rhappyEyeballs\x125\n" +
|
||||||
0x72, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
"\x17trusted_x_forwarded_for\x18\x17 \x03(\tR\x14trustedXForwardedFor\"/\n" +
|
||||||
0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18,
|
"\n" +
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
"TProxyMode\x12\a\n" +
|
||||||
0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
"\x03Off\x10\x00\x12\n" +
|
||||||
0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
"\n" +
|
||||||
0x74, 0x79, 0x70, 0x65, 0x22, 0x89, 0x09, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43,
|
"\x06TProxy\x10\x01\x12\f\n" +
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20,
|
"\bRedirect\x10\x02\"\xad\x01\n" +
|
||||||
0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f,
|
"\x13HappyEyeballsConfig\x12'\n" +
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74,
|
"\x0fprioritize_ipv6\x18\x01 \x01(\bR\x0eprioritizeIpv6\x12\x1e\n" +
|
||||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72,
|
"\n" +
|
||||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
"interleave\x18\x02 \x01(\rR\n" +
|
||||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
"interleave\x12\x1f\n" +
|
||||||
0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74,
|
"\vtry_delayMs\x18\x03 \x01(\x04R\n" +
|
||||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
|
"tryDelayMs\x12,\n" +
|
||||||
0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61,
|
"\x12max_concurrent_try\x18\x04 \x01(\rR\x10maxConcurrentTry*\xa9\x01\n" +
|
||||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65,
|
"\x0eDomainStrategy\x12\t\n" +
|
||||||
0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73,
|
"\x05AS_IS\x10\x00\x12\n" +
|
||||||
0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64,
|
"\n" +
|
||||||
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b,
|
"\x06USE_IP\x10\x01\x12\v\n" +
|
||||||
0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62,
|
"\aUSE_IP4\x10\x02\x12\v\n" +
|
||||||
0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
|
"\aUSE_IP6\x10\x03\x12\f\n" +
|
||||||
0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65,
|
"\bUSE_IP46\x10\x04\x12\f\n" +
|
||||||
0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
"\bUSE_IP64\x10\x05\x12\f\n" +
|
||||||
0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50,
|
"\bFORCE_IP\x10\x06\x12\r\n" +
|
||||||
0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f,
|
"\tFORCE_IP4\x10\a\x12\r\n" +
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
|
"\tFORCE_IP6\x10\b\x12\x0e\n" +
|
||||||
0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
|
"\n" +
|
||||||
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
|
"FORCE_IP46\x10\t\x12\x0e\n" +
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e,
|
"\n" +
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21,
|
"FORCE_IP64\x10\n" +
|
||||||
0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09,
|
"*\x97\x01\n" +
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78,
|
"\x13AddressPortStrategy\x12\b\n" +
|
||||||
0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c,
|
"\x04None\x10\x00\x12\x0f\n" +
|
||||||
0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01,
|
"\vSrvPortOnly\x10\x01\x12\x12\n" +
|
||||||
0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
|
"\x0eSrvAddressOnly\x10\x02\x12\x15\n" +
|
||||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f,
|
"\x11SrvPortAndAddress\x10\x03\x12\x0f\n" +
|
||||||
0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18,
|
"\vTxtPortOnly\x10\x04\x12\x12\n" +
|
||||||
0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,
|
"\x0eTxtAddressOnly\x10\x05\x12\x15\n" +
|
||||||
0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63,
|
"\x11TxtPortAndAddress\x10\x06Bg\n" +
|
||||||
0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
|
"\x1bcom.xray.transport.internetP\x01Z,github.com/xtls/xray-core/transport/internet\xaa\x02\x17Xray.Transport.Internetb\x06proto3"
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_transport_internet_config_proto_rawDescOnce sync.Once
|
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 {
|
func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
||||||
file_transport_internet_config_proto_rawDescOnce.Do(func() {
|
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
|
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
|
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
|
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
|
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
|
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
|
||||||
2, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
|
||||||
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
7, // 6: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||||
6, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
2, // 7: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||||
1, // 8: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
0, // 8: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
8, // 9: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
6, // 9: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||||
10, // [10:10] is the sub-list for method output_type
|
1, // 10: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
||||||
10, // [10:10] is the sub-list for method input_type
|
8, // 11: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
|
||||||
10, // [10:10] is the sub-list for extension type_name
|
12, // [12:12] is the sub-list for method output_type
|
||||||
10, // [10:10] is the sub-list for extension extendee
|
12, // [12:12] is the sub-list for method input_type
|
||||||
0, // [0:10] is the sub-list for field type_name
|
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() }
|
func init() { file_transport_internet_config_proto_init() }
|
||||||
@@ -1019,7 +954,7 @@ func file_transport_internet_config_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
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,
|
NumEnums: 3,
|
||||||
NumMessages: 6,
|
NumMessages: 6,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
@@ -1031,7 +966,6 @@ func file_transport_internet_config_proto_init() {
|
|||||||
MessageInfos: file_transport_internet_config_proto_msgTypes,
|
MessageInfos: file_transport_internet_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_transport_internet_config_proto = out.File
|
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_goTypes = nil
|
||||||
file_transport_internet_config_proto_depIdxs = nil
|
file_transport_internet_config_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ message StreamConfig {
|
|||||||
// Transport security settings. They can be either TLS or REALITY.
|
// Transport security settings. They can be either TLS or REALITY.
|
||||||
repeated xray.common.serial.TypedMessage security_settings = 4;
|
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;
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,243 @@
|
|||||||
|
// 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"`
|
||||||
|
Interval int64 `protobuf:"varint,7,opt,name=interval,proto3" json:"interval,omitempty"`
|
||||||
|
InitStreamReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
|
||||||
|
MaxStreamReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
|
||||||
|
InitConnReceiveWindow uint64 `protobuf:"varint,10,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
|
||||||
|
MaxConnReceiveWindow uint64 `protobuf:"varint,11,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
|
||||||
|
MaxIdleTimeout int64 `protobuf:"varint,12,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
||||||
|
KeepAlivePeriod int64 `protobuf:"varint,13,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
||||||
|
DisablePathMtuDiscovery bool `protobuf:"varint,14,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) GetInterval() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Interval
|
||||||
|
}
|
||||||
|
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\"\xa7\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\x1a\n" +
|
||||||
|
"\binterval\x18\a \x01(\x03R\binterval\x12;\n" +
|
||||||
|
"\x1ainit_stream_receive_window\x18\b \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
|
||||||
|
"\x19max_stream_receive_window\x18\t \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
|
||||||
|
"\x18init_conn_receive_window\x18\n" +
|
||||||
|
" \x01(\x04R\x15initConnReceiveWindow\x125\n" +
|
||||||
|
"\x17max_conn_receive_window\x18\v \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
|
||||||
|
"\x10max_idle_timeout\x18\f \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
||||||
|
"\x11keep_alive_period\x18\r \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
||||||
|
"\x1adisable_path_mtu_discovery\x18\x0e \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,26 @@
|
|||||||
|
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 = 7;
|
||||||
|
|
||||||
|
uint64 init_stream_receive_window = 8;
|
||||||
|
uint64 max_stream_receive_window = 9;
|
||||||
|
uint64 init_conn_receive_window = 10;
|
||||||
|
uint64 max_conn_receive_window = 11;
|
||||||
|
int64 max_idle_timeout = 12;
|
||||||
|
int64 keep_alive_period = 13;
|
||||||
|
bool disable_path_mtu_discovery = 14;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package bbr
|
||||||
|
|
||||||
|
// A RingBuffer is a ring buffer.
|
||||||
|
// It acts as a heap that doesn't cause any allocations.
|
||||||
|
type RingBuffer[T any] struct {
|
||||||
|
ring []T
|
||||||
|
headPos, tailPos int
|
||||||
|
full bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init preallocs a buffer with a certain size.
|
||||||
|
func (r *RingBuffer[T]) Init(size int) {
|
||||||
|
r.ring = make([]T, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the ring buffer.
|
||||||
|
func (r *RingBuffer[T]) Len() int {
|
||||||
|
if r.full {
|
||||||
|
return len(r.ring)
|
||||||
|
}
|
||||||
|
if r.tailPos >= r.headPos {
|
||||||
|
return r.tailPos - r.headPos
|
||||||
|
}
|
||||||
|
return r.tailPos - r.headPos + len(r.ring)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty says if the ring buffer is empty.
|
||||||
|
func (r *RingBuffer[T]) Empty() bool {
|
||||||
|
return !r.full && r.headPos == r.tailPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBack adds a new element.
|
||||||
|
// If the ring buffer is full, its capacity is increased first.
|
||||||
|
func (r *RingBuffer[T]) PushBack(t T) {
|
||||||
|
if r.full || len(r.ring) == 0 {
|
||||||
|
r.grow()
|
||||||
|
}
|
||||||
|
r.ring[r.tailPos] = t
|
||||||
|
r.tailPos++
|
||||||
|
if r.tailPos == len(r.ring) {
|
||||||
|
r.tailPos = 0
|
||||||
|
}
|
||||||
|
if r.tailPos == r.headPos {
|
||||||
|
r.full = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopFront returns the next element.
|
||||||
|
// It must not be called when the buffer is empty, that means that
|
||||||
|
// callers might need to check if there are elements in the buffer first.
|
||||||
|
func (r *RingBuffer[T]) PopFront() T {
|
||||||
|
if r.Empty() {
|
||||||
|
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue")
|
||||||
|
}
|
||||||
|
r.full = false
|
||||||
|
t := r.ring[r.headPos]
|
||||||
|
r.ring[r.headPos] = *new(T)
|
||||||
|
r.headPos++
|
||||||
|
if r.headPos == len(r.ring) {
|
||||||
|
r.headPos = 0
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset returns the offset element.
|
||||||
|
// It must not be called when the buffer is empty, that means that
|
||||||
|
// callers might need to check if there are elements in the buffer first
|
||||||
|
// and check if the index larger than buffer length.
|
||||||
|
func (r *RingBuffer[T]) Offset(index int) *T {
|
||||||
|
if r.Empty() || index >= r.Len() {
|
||||||
|
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: offset from invalid index")
|
||||||
|
}
|
||||||
|
offset := (r.headPos + index) % len(r.ring)
|
||||||
|
return &r.ring[offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Front returns the front element.
|
||||||
|
// It must not be called when the buffer is empty, that means that
|
||||||
|
// callers might need to check if there are elements in the buffer first.
|
||||||
|
func (r *RingBuffer[T]) Front() *T {
|
||||||
|
if r.Empty() {
|
||||||
|
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: front from an empty queue")
|
||||||
|
}
|
||||||
|
return &r.ring[r.headPos]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back returns the back element.
|
||||||
|
// It must not be called when the buffer is empty, that means that
|
||||||
|
// callers might need to check if there are elements in the buffer first.
|
||||||
|
func (r *RingBuffer[T]) Back() *T {
|
||||||
|
if r.Empty() {
|
||||||
|
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: back from an empty queue")
|
||||||
|
}
|
||||||
|
return r.Offset(r.Len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow the maximum size of the queue.
|
||||||
|
// This method assume the queue is full.
|
||||||
|
func (r *RingBuffer[T]) grow() {
|
||||||
|
oldRing := r.ring
|
||||||
|
newSize := len(oldRing) * 2
|
||||||
|
if newSize == 0 {
|
||||||
|
newSize = 1
|
||||||
|
}
|
||||||
|
r.ring = make([]T, newSize)
|
||||||
|
headLen := copy(r.ring, oldRing[r.headPos:])
|
||||||
|
copy(r.ring[headLen:], oldRing[:r.headPos])
|
||||||
|
r.headPos, r.tailPos, r.full = 0, len(oldRing), false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all elements.
|
||||||
|
func (r *RingBuffer[T]) Clear() {
|
||||||
|
var zeroValue T
|
||||||
|
for i := range r.ring {
|
||||||
|
r.ring[i] = zeroValue
|
||||||
|
}
|
||||||
|
r.headPos, r.tailPos, r.full = 0, 0, false
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package bbr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum)
|
||||||
|
// estimate of a stream of samples over some fixed time interval. (E.g.,
|
||||||
|
// the minimum RTT over the past five minutes.) The algorithm keeps track of
|
||||||
|
// the best, second best, and third best min (or max) estimates, maintaining an
|
||||||
|
// invariant that the measurement time of the n'th best >= n-1'th best.
|
||||||
|
|
||||||
|
// The algorithm works as follows. On a reset, all three estimates are set to
|
||||||
|
// the same sample. The second best estimate is then recorded in the second
|
||||||
|
// quarter of the window, and a third best estimate is recorded in the second
|
||||||
|
// half of the window, bounding the worst case error when the true min is
|
||||||
|
// monotonically increasing (or true max is monotonically decreasing) over the
|
||||||
|
// window.
|
||||||
|
//
|
||||||
|
// A new best sample replaces all three estimates, since the new best is lower
|
||||||
|
// (or higher) than everything else in the window and it is the most recent.
|
||||||
|
// The window thus effectively gets reset on every new min. The same property
|
||||||
|
// holds true for second best and third best estimates. Specifically, when a
|
||||||
|
// sample arrives that is better than the second best but not better than the
|
||||||
|
// best, it replaces the second and third best estimates but not the best
|
||||||
|
// estimate. Similarly, a sample that is better than the third best estimate
|
||||||
|
// but not the other estimates replaces only the third best estimate.
|
||||||
|
//
|
||||||
|
// Finally, when the best expires, it is replaced by the second best, which in
|
||||||
|
// turn is replaced by the third best. The newest sample replaces the third
|
||||||
|
// best.
|
||||||
|
|
||||||
|
type WindowedFilterValue interface {
|
||||||
|
any
|
||||||
|
}
|
||||||
|
|
||||||
|
type WindowedFilterTime interface {
|
||||||
|
constraints.Integer | constraints.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
type WindowedFilter[V WindowedFilterValue, T WindowedFilterTime] struct {
|
||||||
|
// Time length of window.
|
||||||
|
windowLength T
|
||||||
|
estimates []entry[V, T]
|
||||||
|
comparator func(V, V) int
|
||||||
|
}
|
||||||
|
|
||||||
|
type entry[V WindowedFilterValue, T WindowedFilterTime] struct {
|
||||||
|
sample V
|
||||||
|
time T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares two values and returns true if the first is greater than or equal
|
||||||
|
// to the second.
|
||||||
|
func MaxFilter[O constraints.Ordered](a, b O) int {
|
||||||
|
if a > b {
|
||||||
|
return 1
|
||||||
|
} else if a < b {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares two values and returns true if the first is less than or equal
|
||||||
|
// to the second.
|
||||||
|
func MinFilter[O constraints.Ordered](a, b O) int {
|
||||||
|
if a < b {
|
||||||
|
return 1
|
||||||
|
} else if a > b {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWindowedFilter[V WindowedFilterValue, T WindowedFilterTime](windowLength T, comparator func(V, V) int) *WindowedFilter[V, T] {
|
||||||
|
return &WindowedFilter[V, T]{
|
||||||
|
windowLength: windowLength,
|
||||||
|
estimates: make([]entry[V, T], 3, 3),
|
||||||
|
comparator: comparator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes the window length. Does not update any current samples.
|
||||||
|
func (f *WindowedFilter[V, T]) SetWindowLength(windowLength T) {
|
||||||
|
f.windowLength = windowLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WindowedFilter[V, T]) GetBest() V {
|
||||||
|
return f.estimates[0].sample
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WindowedFilter[V, T]) GetSecondBest() V {
|
||||||
|
return f.estimates[1].sample
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WindowedFilter[V, T]) GetThirdBest() V {
|
||||||
|
return f.estimates[2].sample
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates best estimates with |sample|, and expires and updates best
|
||||||
|
// estimates as necessary.
|
||||||
|
func (f *WindowedFilter[V, T]) Update(newSample V, newTime T) {
|
||||||
|
// Reset all estimates if they have not yet been initialized, if new sample
|
||||||
|
// is a new best, or if the newest recorded estimate is too old.
|
||||||
|
if f.comparator(f.estimates[0].sample, *new(V)) == 0 ||
|
||||||
|
f.comparator(newSample, f.estimates[0].sample) >= 0 ||
|
||||||
|
newTime-f.estimates[2].time > f.windowLength {
|
||||||
|
f.Reset(newSample, newTime)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.comparator(newSample, f.estimates[1].sample) >= 0 {
|
||||||
|
f.estimates[1] = entry[V, T]{newSample, newTime}
|
||||||
|
f.estimates[2] = f.estimates[1]
|
||||||
|
} else if f.comparator(newSample, f.estimates[2].sample) >= 0 {
|
||||||
|
f.estimates[2] = entry[V, T]{newSample, newTime}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire and update estimates as necessary.
|
||||||
|
if newTime-f.estimates[0].time > f.windowLength {
|
||||||
|
// The best estimate hasn't been updated for an entire window, so promote
|
||||||
|
// second and third best estimates.
|
||||||
|
f.estimates[0] = f.estimates[1]
|
||||||
|
f.estimates[1] = f.estimates[2]
|
||||||
|
f.estimates[2] = entry[V, T]{newSample, newTime}
|
||||||
|
// Need to iterate one more time. Check if the new best estimate is
|
||||||
|
// outside the window as well, since it may also have been recorded a
|
||||||
|
// long time ago. Don't need to iterate once more since we cover that
|
||||||
|
// case at the beginning of the method.
|
||||||
|
if newTime-f.estimates[0].time > f.windowLength {
|
||||||
|
f.estimates[0] = f.estimates[1]
|
||||||
|
f.estimates[1] = f.estimates[2]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f.comparator(f.estimates[1].sample, f.estimates[0].sample) == 0 &&
|
||||||
|
newTime-f.estimates[1].time > f.windowLength/4 {
|
||||||
|
// A quarter of the window has passed without a better sample, so the
|
||||||
|
// second-best estimate is taken from the second quarter of the window.
|
||||||
|
f.estimates[1] = entry[V, T]{newSample, newTime}
|
||||||
|
f.estimates[2] = f.estimates[1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.comparator(f.estimates[2].sample, f.estimates[1].sample) == 0 &&
|
||||||
|
newTime-f.estimates[2].time > f.windowLength/2 {
|
||||||
|
// We've passed a half of the window without a better estimate, so take
|
||||||
|
// a third-best estimate from the second half of the window.
|
||||||
|
f.estimates[2] = entry[V, T]{newSample, newTime}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets all estimates to new sample.
|
||||||
|
func (f *WindowedFilter[V, T]) Reset(newSample V, newTime T) {
|
||||||
|
f.estimates[2] = entry[V, T]{newSample, newTime}
|
||||||
|
f.estimates[1] = f.estimates[2]
|
||||||
|
f.estimates[0] = f.estimates[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WindowedFilter[V, T]) Clear() {
|
||||||
|
f.estimates = make([]entry[V, T], 3, 3)
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package brutal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/common"
|
||||||
|
|
||||||
|
"github.com/apernet/quic-go/congestion"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pktInfoSlotCount = 5 // slot index is based on seconds, so this is basically how many seconds we sample
|
||||||
|
minSampleCount = 50
|
||||||
|
minAckRate = 0.8
|
||||||
|
congestionWindowMultiplier = 2
|
||||||
|
|
||||||
|
debugEnv = "HYSTERIA_BRUTAL_DEBUG"
|
||||||
|
debugPrintInterval = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ congestion.CongestionControl = &BrutalSender{}
|
||||||
|
|
||||||
|
type BrutalSender struct {
|
||||||
|
rttStats congestion.RTTStatsProvider
|
||||||
|
bps congestion.ByteCount
|
||||||
|
maxDatagramSize congestion.ByteCount
|
||||||
|
pacer *common.Pacer
|
||||||
|
|
||||||
|
pktInfoSlots [pktInfoSlotCount]pktInfo
|
||||||
|
ackRate float64
|
||||||
|
|
||||||
|
debug bool
|
||||||
|
lastAckPrintTimestamp int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type pktInfo struct {
|
||||||
|
Timestamp int64
|
||||||
|
AckCount uint64
|
||||||
|
LossCount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBrutalSender(bps uint64) *BrutalSender {
|
||||||
|
debug, _ := strconv.ParseBool(os.Getenv(debugEnv))
|
||||||
|
bs := &BrutalSender{
|
||||||
|
bps: congestion.ByteCount(bps),
|
||||||
|
maxDatagramSize: congestion.InitialPacketSize,
|
||||||
|
ackRate: 1,
|
||||||
|
debug: debug,
|
||||||
|
}
|
||||||
|
bs.pacer = common.NewPacer(func() congestion.ByteCount {
|
||||||
|
return congestion.ByteCount(float64(bs.bps) / bs.ackRate)
|
||||||
|
})
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) SetRTTStatsProvider(rttStats congestion.RTTStatsProvider) {
|
||||||
|
b.rttStats = rttStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) congestion.Time {
|
||||||
|
return b.pacer.TimeUntilSend()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) HasPacingBudget(now congestion.Time) bool {
|
||||||
|
return b.pacer.Budget(now) >= b.maxDatagramSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool {
|
||||||
|
return bytesInFlight <= b.GetCongestionWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) GetCongestionWindow() congestion.ByteCount {
|
||||||
|
rtt := b.rttStats.SmoothedRTT()
|
||||||
|
if rtt <= 0 {
|
||||||
|
return 10240
|
||||||
|
}
|
||||||
|
cwnd := congestion.ByteCount(float64(b.bps) * rtt.Seconds() * congestionWindowMultiplier / b.ackRate)
|
||||||
|
if cwnd < b.maxDatagramSize {
|
||||||
|
cwnd = b.maxDatagramSize
|
||||||
|
}
|
||||||
|
return cwnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnPacketSent(sentTime congestion.Time, bytesInFlight congestion.ByteCount,
|
||||||
|
packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool,
|
||||||
|
) {
|
||||||
|
b.pacer.SentPacket(sentTime, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount,
|
||||||
|
priorInFlight congestion.ByteCount, eventTime congestion.Time,
|
||||||
|
) {
|
||||||
|
// Stub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes congestion.ByteCount,
|
||||||
|
priorInFlight congestion.ByteCount,
|
||||||
|
) {
|
||||||
|
// Stub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime congestion.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) {
|
||||||
|
currentTimestamp := int64(time.Duration(eventTime) / time.Second)
|
||||||
|
slot := currentTimestamp % pktInfoSlotCount
|
||||||
|
if b.pktInfoSlots[slot].Timestamp == currentTimestamp {
|
||||||
|
b.pktInfoSlots[slot].LossCount += uint64(len(lostPackets))
|
||||||
|
b.pktInfoSlots[slot].AckCount += uint64(len(ackedPackets))
|
||||||
|
} else {
|
||||||
|
// uninitialized slot or too old, reset
|
||||||
|
b.pktInfoSlots[slot].Timestamp = currentTimestamp
|
||||||
|
b.pktInfoSlots[slot].AckCount = uint64(len(ackedPackets))
|
||||||
|
b.pktInfoSlots[slot].LossCount = uint64(len(lostPackets))
|
||||||
|
}
|
||||||
|
b.updateAckRate(currentTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) SetMaxDatagramSize(size congestion.ByteCount) {
|
||||||
|
b.maxDatagramSize = size
|
||||||
|
b.pacer.SetMaxDatagramSize(size)
|
||||||
|
if b.debug {
|
||||||
|
b.debugPrint("SetMaxDatagramSize: %d", size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) updateAckRate(currentTimestamp int64) {
|
||||||
|
minTimestamp := currentTimestamp - pktInfoSlotCount
|
||||||
|
var ackCount, lossCount uint64
|
||||||
|
for _, info := range b.pktInfoSlots {
|
||||||
|
if info.Timestamp < minTimestamp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ackCount += info.AckCount
|
||||||
|
lossCount += info.LossCount
|
||||||
|
}
|
||||||
|
if ackCount+lossCount < minSampleCount {
|
||||||
|
b.ackRate = 1
|
||||||
|
if b.canPrintAckRate(currentTimestamp) {
|
||||||
|
b.lastAckPrintTimestamp = currentTimestamp
|
||||||
|
b.debugPrint("Not enough samples (total=%d, ack=%d, loss=%d, rtt=%d)",
|
||||||
|
ackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rate := float64(ackCount) / float64(ackCount+lossCount)
|
||||||
|
if rate < minAckRate {
|
||||||
|
b.ackRate = minAckRate
|
||||||
|
if b.canPrintAckRate(currentTimestamp) {
|
||||||
|
b.lastAckPrintTimestamp = currentTimestamp
|
||||||
|
b.debugPrint("ACK rate too low: %.2f, clamped to %.2f (total=%d, ack=%d, loss=%d, rtt=%d)",
|
||||||
|
rate, minAckRate, ackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.ackRate = rate
|
||||||
|
if b.canPrintAckRate(currentTimestamp) {
|
||||||
|
b.lastAckPrintTimestamp = currentTimestamp
|
||||||
|
b.debugPrint("ACK rate: %.2f (total=%d, ack=%d, loss=%d, rtt=%d)",
|
||||||
|
rate, ackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) InSlowStart() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) InRecovery() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) MaybeExitSlowStart() {}
|
||||||
|
|
||||||
|
func (b *BrutalSender) OnRetransmissionTimeout(packetsRetransmitted bool) {}
|
||||||
|
|
||||||
|
func (b *BrutalSender) canPrintAckRate(currentTimestamp int64) bool {
|
||||||
|
return b.debug && currentTimestamp-b.lastAckPrintTimestamp >= debugPrintInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrutalSender) debugPrint(format string, a ...any) {
|
||||||
|
fmt.Printf("[BrutalSender] [%s] %s\n",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apernet/quic-go/congestion"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxBurstPackets = 10
|
||||||
|
maxBurstPacingDelayMultiplier = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pacer implements a token bucket pacing algorithm.
|
||||||
|
type Pacer struct {
|
||||||
|
budgetAtLastSent congestion.ByteCount
|
||||||
|
maxDatagramSize congestion.ByteCount
|
||||||
|
lastSentTime congestion.Time
|
||||||
|
getBandwidth func() congestion.ByteCount // in bytes/s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacer(getBandwidth func() congestion.ByteCount) *Pacer {
|
||||||
|
p := &Pacer{
|
||||||
|
budgetAtLastSent: maxBurstPackets * congestion.InitialPacketSize,
|
||||||
|
maxDatagramSize: congestion.InitialPacketSize,
|
||||||
|
getBandwidth: getBandwidth,
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacer) SentPacket(sendTime congestion.Time, size congestion.ByteCount) {
|
||||||
|
budget := p.Budget(sendTime)
|
||||||
|
if size > budget {
|
||||||
|
p.budgetAtLastSent = 0
|
||||||
|
} else {
|
||||||
|
p.budgetAtLastSent = budget - size
|
||||||
|
}
|
||||||
|
p.lastSentTime = sendTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacer) Budget(now congestion.Time) congestion.ByteCount {
|
||||||
|
if p.lastSentTime.IsZero() {
|
||||||
|
return p.maxBurstSize()
|
||||||
|
}
|
||||||
|
budget := p.budgetAtLastSent + (p.getBandwidth()*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9
|
||||||
|
if budget < 0 { // protect against overflows
|
||||||
|
budget = congestion.ByteCount(1<<62 - 1)
|
||||||
|
}
|
||||||
|
return min(p.maxBurstSize(), budget)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacer) maxBurstSize() congestion.ByteCount {
|
||||||
|
return max(
|
||||||
|
congestion.ByteCount((maxBurstPacingDelayMultiplier*congestion.MinPacingDelay).Nanoseconds())*p.getBandwidth()/1e9,
|
||||||
|
maxBurstPackets*p.maxDatagramSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeUntilSend returns when the next packet should be sent.
|
||||||
|
// It returns the zero value if a packet can be sent immediately.
|
||||||
|
func (p *Pacer) TimeUntilSend() congestion.Time {
|
||||||
|
if p.budgetAtLastSent >= p.maxDatagramSize {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
|
||||||
|
bw := uint64(p.getBandwidth())
|
||||||
|
// We might need to round up this value.
|
||||||
|
// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.
|
||||||
|
d := diff / bw
|
||||||
|
// this is effectively a math.Ceil, but using only integer math
|
||||||
|
if diff%bw > 0 {
|
||||||
|
d++
|
||||||
|
}
|
||||||
|
return p.lastSentTime.Add(max(congestion.MinPacingDelay, time.Duration(d)*time.Nanosecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacer) SetMaxDatagramSize(s congestion.ByteCount) {
|
||||||
|
p.maxDatagramSize = s
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package congestion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/apernet/quic-go"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/brutal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UseBBR(conn *quic.Conn) {
|
||||||
|
conn.SetCongestionControl(bbr.NewBbrSender(
|
||||||
|
bbr.DefaultClock{},
|
||||||
|
bbr.GetInitialPacketSize(conn.RemoteAddr()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UseBrutal(conn *quic.Conn, tx uint64) {
|
||||||
|
conn.SetCongestionControl(brutal.NewBrutalSender(tx))
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apernet/quic-go"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type interConn struct {
|
||||||
|
stream *quic.Stream
|
||||||
|
local net.Addr
|
||||||
|
remote net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) Read(b []byte) (int, error) {
|
||||||
|
return i.stream.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) Write(b []byte) (int, error) {
|
||||||
|
return i.stream.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) Close() error {
|
||||||
|
i.stream.CancelRead(0)
|
||||||
|
return i.stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) LocalAddr() net.Addr {
|
||||||
|
return i.local
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) RemoteAddr() net.Addr {
|
||||||
|
return i.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) SetDeadline(t time.Time) error {
|
||||||
|
return i.stream.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return i.stream.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *interConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return i.stream.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InterUdpConn struct {
|
||||||
|
conn *quic.Conn
|
||||||
|
local net.Addr
|
||||||
|
remote net.Addr
|
||||||
|
|
||||||
|
id uint32
|
||||||
|
ch chan []byte
|
||||||
|
closed bool
|
||||||
|
closeFunc func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) Read(p []byte) (int, error) {
|
||||||
|
b, ok := <-i.ch
|
||||||
|
if !ok {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n := copy(p, b)
|
||||||
|
if n != len(b) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) Write(p []byte) (int, error) {
|
||||||
|
binary.BigEndian.PutUint32(p, i.id)
|
||||||
|
if err := i.conn.SendDatagram(p); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) Close() error {
|
||||||
|
i.closeFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) LocalAddr() net.Addr {
|
||||||
|
return i.local
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) RemoteAddr() net.Addr {
|
||||||
|
return i.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InterUdpConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
package hysteria
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
go_tls "crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apernet/quic-go"
|
||||||
|
"github.com/apernet/quic-go/http3"
|
||||||
|
"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/task"
|
||||||
|
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type udpSessionManager struct {
|
||||||
|
conn *quic.Conn
|
||||||
|
m map[uint32]*InterUdpConn
|
||||||
|
nextId uint32
|
||||||
|
closed bool
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpSessionManager) run() {
|
||||||
|
for {
|
||||||
|
d, err := m.conn.ReceiveDatagram(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d) < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sessionId := binary.BigEndian.Uint32(d[:4])
|
||||||
|
|
||||||
|
m.feed(sessionId, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
m.closed = true
|
||||||
|
for _, udpConn := range m.m {
|
||||||
|
m.close(udpConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpSessionManager) close(udpConn *InterUdpConn) {
|
||||||
|
if !udpConn.closed {
|
||||||
|
udpConn.closed = true
|
||||||
|
close(udpConn.ch)
|
||||||
|
delete(m.m, udpConn.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
if m.closed {
|
||||||
|
return nil, errors.New("closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
udpConn := &InterUdpConn{
|
||||||
|
conn: m.conn,
|
||||||
|
local: m.conn.LocalAddr(),
|
||||||
|
remote: m.conn.RemoteAddr(),
|
||||||
|
|
||||||
|
id: m.nextId,
|
||||||
|
ch: make(chan []byte, udpMessageChanSize),
|
||||||
|
}
|
||||||
|
udpConn.closeFunc = func() {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
m.close(udpConn)
|
||||||
|
}
|
||||||
|
m.m[m.nextId] = udpConn
|
||||||
|
m.nextId++
|
||||||
|
|
||||||
|
return udpConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpSessionManager) feed(sessionId uint32, d []byte) {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
udpConn, ok := m.m[sessionId]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case udpConn.ch <- d:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
ctx context.Context
|
||||||
|
dest net.Destination
|
||||||
|
pktConn net.PacketConn
|
||||||
|
conn *quic.Conn
|
||||||
|
config *Config
|
||||||
|
tlsConfig *go_tls.Config
|
||||||
|
socketConfig *internet.SocketConfig
|
||||||
|
udpmaskManager *finalmask.UdpmaskManager
|
||||||
|
udpSM *udpSessionManager
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) status() Status {
|
||||||
|
if c.conn == nil {
|
||||||
|
return StatusUnknown
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-c.conn.Context().Done():
|
||||||
|
return StatusInactive
|
||||||
|
default:
|
||||||
|
return StatusActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) close() {
|
||||||
|
_ = c.conn.CloseWithError(closeErrCodeOK, "")
|
||||||
|
_ = c.pktConn.Close()
|
||||||
|
c.pktConn = nil
|
||||||
|
c.conn = nil
|
||||||
|
c.udpSM = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) dial() error {
|
||||||
|
status := c.status()
|
||||||
|
if status == StatusActive {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if status == StatusInactive {
|
||||||
|
c.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var index int
|
||||||
|
if len(c.config.Ports) > 0 {
|
||||||
|
index = rand.Intn(len(c.config.Ports))
|
||||||
|
c.dest.Port = net.Port(c.config.Ports[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := internet.DialSystem(c.ctx, c.dest, c.socketConfig)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to dial to dest").Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remote := raw.RemoteAddr()
|
||||||
|
|
||||||
|
pktConn, ok := raw.(net.PacketConn)
|
||||||
|
if !ok {
|
||||||
|
raw.Close()
|
||||||
|
return errors.New("raw is not PacketConn")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.config.Ports) > 0 {
|
||||||
|
addr := &udphop.UDPHopAddr{
|
||||||
|
IP: remote.(*net.UDPAddr).IP,
|
||||||
|
Ports: c.config.Ports,
|
||||||
|
}
|
||||||
|
pktConn, err = udphop.NewUDPHopPacketConn(addr, time.Duration(c.config.Interval)*time.Second, c.udphopDialer, pktConn, index)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("udphop err").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.udpmaskManager != nil {
|
||||||
|
pktConn, err = c.udpmaskManager.WrapPacketConnClient(pktConn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("mask err").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var quicConn *quic.Conn
|
||||||
|
rt := &http3.Transport{
|
||||||
|
TLSClientConfig: c.tlsConfig,
|
||||||
|
QUICConfig: &quic.Config{
|
||||||
|
InitialStreamReceiveWindow: c.config.InitStreamReceiveWindow,
|
||||||
|
MaxStreamReceiveWindow: c.config.MaxStreamReceiveWindow,
|
||||||
|
InitialConnectionReceiveWindow: c.config.InitConnReceiveWindow,
|
||||||
|
MaxConnectionReceiveWindow: c.config.MaxConnReceiveWindow,
|
||||||
|
MaxIdleTimeout: time.Duration(c.config.MaxIdleTimeout) * time.Second,
|
||||||
|
KeepAlivePeriod: time.Duration(c.config.KeepAlivePeriod) * time.Second,
|
||||||
|
DisablePathMTUDiscovery: c.config.DisablePathMtuDiscovery,
|
||||||
|
EnableDatagrams: true,
|
||||||
|
MaxDatagramFrameSize: MaxDatagramFrameSize,
|
||||||
|
DisablePathManager: true,
|
||||||
|
},
|
||||||
|
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||||
|
qc, err := quic.DialEarly(ctx, pktConn, remote, tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
quicConn = qc
|
||||||
|
return qc, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: URLHost,
|
||||||
|
Path: URLPath,
|
||||||
|
},
|
||||||
|
Header: http.Header{
|
||||||
|
RequestHeaderAuth: []string{c.config.Auth},
|
||||||
|
CommonHeaderCCRX: []string{strconv.FormatUint(c.config.Down, 10)},
|
||||||
|
CommonHeaderPadding: []string{authRequestPadding.String()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
if quicConn != nil {
|
||||||
|
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
|
||||||
|
}
|
||||||
|
_ = pktConn.Close()
|
||||||
|
return errors.New("RoundTrip err").Base(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != StatusAuthOK {
|
||||||
|
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
|
||||||
|
_ = pktConn.Close()
|
||||||
|
return errors.New("auth failed")
|
||||||
|
}
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
|
serverUdp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))
|
||||||
|
serverAuto := resp.Header.Get(CommonHeaderCCRX)
|
||||||
|
serverDown, _ := strconv.ParseUint(serverAuto, 10, 64)
|
||||||
|
|
||||||
|
switch c.config.Congestion {
|
||||||
|
case "reno":
|
||||||
|
errors.LogDebug(c.ctx, "congestion reno")
|
||||||
|
case "bbr":
|
||||||
|
errors.LogDebug(c.ctx, "congestion bbr")
|
||||||
|
congestion.UseBBR(quicConn)
|
||||||
|
case "brutal", "":
|
||||||
|
if serverAuto == "auto" || c.config.Up == 0 || serverDown == 0 {
|
||||||
|
errors.LogDebug(c.ctx, "congestion bbr")
|
||||||
|
congestion.UseBBR(quicConn)
|
||||||
|
} else {
|
||||||
|
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(c.config.Up, serverDown))
|
||||||
|
congestion.UseBrutal(quicConn, min(c.config.Up, serverDown))
|
||||||
|
}
|
||||||
|
case "force-brutal":
|
||||||
|
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", c.config.Up)
|
||||||
|
congestion.UseBrutal(quicConn, c.config.Up)
|
||||||
|
default:
|
||||||
|
errors.LogDebug(c.ctx, "congestion reno")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.pktConn = pktConn
|
||||||
|
c.conn = quicConn
|
||||||
|
if serverUdp {
|
||||||
|
c.udpSM = &udpSessionManager{
|
||||||
|
conn: quicConn,
|
||||||
|
m: make(map[uint32]*InterUdpConn),
|
||||||
|
nextId: 1,
|
||||||
|
}
|
||||||
|
go c.udpSM.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) clean() {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.status() == StatusInactive {
|
||||||
|
c.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) tcp() (stat.Connection, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
err := c.dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := c.conn.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &interConn{
|
||||||
|
stream: stream,
|
||||||
|
local: c.conn.LocalAddr(),
|
||||||
|
remote: c.conn.RemoteAddr(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) udp() (stat.Connection, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
err := c.dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.udpSM == nil {
|
||||||
|
return nil, errors.New("server does not support udp")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.udpSM.udp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) setCtx(ctx context.Context) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
c.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.status() != StatusActive {
|
||||||
|
errors.LogDebug(c.ctx, "stop hop on disconnected QUIC waiting to be closed")
|
||||||
|
return nil, errors.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := internet.DialSystem(c.ctx, net.DestinationFromAddr(addr), c.socketConfig)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogDebug(c.ctx, "failed to dial to dest skip hop")
|
||||||
|
return nil, errors.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
pktConn, ok := raw.(net.PacketConn)
|
||||||
|
if !ok {
|
||||||
|
errors.LogDebug(c.ctx, "raw is not PacketConn skip hop")
|
||||||
|
raw.Close()
|
||||||
|
return nil, errors.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
return pktConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientManager struct {
|
||||||
|
m map[string]*client
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *clientManager) clean() {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, c := range m.m {
|
||||||
|
c.clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var manger *clientManager
|
||||||
|
|
||||||
|
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
|
||||||
|
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return nil, errors.New("tls config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
requireDatagram := hyCtx.RequireDatagramFromContext(ctx)
|
||||||
|
addr := dest.NetAddr()
|
||||||
|
config := streamSettings.ProtocolSettings.(*Config)
|
||||||
|
|
||||||
|
manger.mutex.Lock()
|
||||||
|
c, ok := manger.m[addr]
|
||||||
|
if !ok {
|
||||||
|
dest.Network = net.Network_UDP
|
||||||
|
c = &client{
|
||||||
|
ctx: ctx,
|
||||||
|
dest: dest,
|
||||||
|
config: config,
|
||||||
|
tlsConfig: tlsConfig.GetTLSConfig(),
|
||||||
|
socketConfig: streamSettings.SocketSettings,
|
||||||
|
udpmaskManager: streamSettings.UdpmaskManager,
|
||||||
|
}
|
||||||
|
manger.m[addr] = c
|
||||||
|
}
|
||||||
|
c.setCtx(ctx)
|
||||||
|
manger.mutex.Unlock()
|
||||||
|
|
||||||
|
if requireDatagram {
|
||||||
|
return c.udp()
|
||||||
|
}
|
||||||
|
return c.tcp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
manger = &clientManager{
|
||||||
|
m: make(map[string]*client),
|
||||||
|
}
|
||||||
|
(&task.Periodic{
|
||||||
|
Interval: 30 * time.Second,
|
||||||
|
Execute: func() error {
|
||||||
|
manger.clean()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}).Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package padding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
// padding specifies a half-open range [Min, Max).
|
||||||
|
type Padding struct {
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Padding) String() string {
|
||||||
|
n := p.Min + rand.Intn(p.Max-p.Min)
|
||||||
|
bs := make([]byte, n)
|
||||||
|
for i := range bs {
|
||||||
|
bs[i] = paddingChars[rand.Intn(len(paddingChars))]
|
||||||
|
}
|
||||||
|
return string(bs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package udphop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InvalidPortError struct {
|
||||||
|
PortStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidPortError) Error() string {
|
||||||
|
return fmt.Sprintf("%s is not a valid port number or range", e.PortStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPHopAddr contains an IP address and a list of ports.
|
||||||
|
type UDPHopAddr struct {
|
||||||
|
IP net.IP
|
||||||
|
Ports []uint32
|
||||||
|
PortStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UDPHopAddr) Network() string {
|
||||||
|
return "udphop"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *UDPHopAddr) String() string {
|
||||||
|
return net.JoinHostPort(a.IP.String(), a.PortStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addrs returns a list of net.Addr's, one for each port.
|
||||||
|
func (a *UDPHopAddr) addrs() ([]net.Addr, error) {
|
||||||
|
var addrs []net.Addr
|
||||||
|
for _, port := range a.Ports {
|
||||||
|
addr := &net.UDPAddr{
|
||||||
|
IP: a.IP,
|
||||||
|
Port: int(port),
|
||||||
|
}
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func ResolveUDPHopAddr(addr string) (*UDPHopAddr, error) {
|
||||||
|
// host, portStr, err := net.SplitHostPort(addr)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// ip, err := net.ResolveIPAddr("ip", host)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// result := &UDPHopAddr{
|
||||||
|
// IP: ip.IP,
|
||||||
|
// PortStr: portStr,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pu := utils.ParsePortUnion(portStr)
|
||||||
|
// if pu == nil {
|
||||||
|
// return nil, InvalidPortError{portStr}
|
||||||
|
// }
|
||||||
|
// result.Ports = pu.Ports()
|
||||||
|
|
||||||
|
// return result, nil
|
||||||
|
// }
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package udphop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
packetQueueSize = 1024
|
||||||
|
udpBufferSize = 2048 // QUIC packets are at most 1500 bytes long, so 2k should be more than enough
|
||||||
|
|
||||||
|
defaultHopInterval = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type udpHopPacketConn struct {
|
||||||
|
Addr net.Addr
|
||||||
|
Addrs []net.Addr
|
||||||
|
HopInterval time.Duration
|
||||||
|
ListenUDPFunc ListenUDPFunc
|
||||||
|
|
||||||
|
connMutex sync.RWMutex
|
||||||
|
prevConn net.PacketConn
|
||||||
|
currentConn net.PacketConn
|
||||||
|
addrIndex int
|
||||||
|
|
||||||
|
readBufferSize int
|
||||||
|
writeBufferSize int
|
||||||
|
|
||||||
|
recvQueue chan *udpPacket
|
||||||
|
closeChan chan struct{}
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
bufPool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpPacket struct {
|
||||||
|
Buf []byte
|
||||||
|
N int
|
||||||
|
Addr net.Addr
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error)
|
||||||
|
|
||||||
|
func NewUDPHopPacketConn(addr *UDPHopAddr, hopInterval time.Duration, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn, index int) (net.PacketConn, error) {
|
||||||
|
if hopInterval == 0 {
|
||||||
|
hopInterval = defaultHopInterval
|
||||||
|
} else if hopInterval < 5*time.Second {
|
||||||
|
return nil, errors.New("hop interval must be at least 5 seconds")
|
||||||
|
}
|
||||||
|
// if listenUDPFunc == nil {
|
||||||
|
// listenUDPFunc = func() (net.PacketConn, error) {
|
||||||
|
// return net.ListenUDP("udp", nil)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if listenUDPFunc == nil {
|
||||||
|
return nil, errors.New("nil listenUDPFunc")
|
||||||
|
}
|
||||||
|
addrs, err := addr.addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// curConn, err := listenUDPFunc()
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
hConn := &udpHopPacketConn{
|
||||||
|
Addr: addr,
|
||||||
|
Addrs: addrs,
|
||||||
|
HopInterval: hopInterval,
|
||||||
|
ListenUDPFunc: listenUDPFunc,
|
||||||
|
prevConn: nil,
|
||||||
|
currentConn: pktConn,
|
||||||
|
addrIndex: index,
|
||||||
|
recvQueue: make(chan *udpPacket, packetQueueSize),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
bufPool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return make([]byte, udpBufferSize)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go hConn.recvLoop(pktConn)
|
||||||
|
go hConn.hopLoop()
|
||||||
|
return hConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) recvLoop(conn net.PacketConn) {
|
||||||
|
for {
|
||||||
|
buf := u.bufPool.Get().([]byte)
|
||||||
|
n, addr, err := conn.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
u.bufPool.Put(buf)
|
||||||
|
var netErr net.Error
|
||||||
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||||
|
// Only pass through timeout errors here, not permanent errors
|
||||||
|
// like connection closed. Connection close is normal as we close
|
||||||
|
// the old connection to exit this loop every time we hop.
|
||||||
|
u.recvQueue <- &udpPacket{nil, 0, nil, netErr}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case u.recvQueue <- &udpPacket{buf, n, addr, nil}:
|
||||||
|
// Packet successfully queued
|
||||||
|
default:
|
||||||
|
// Queue is full, drop the packet
|
||||||
|
u.bufPool.Put(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) hopLoop() {
|
||||||
|
ticker := time.NewTicker(u.HopInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
u.hop()
|
||||||
|
case <-u.closeChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) hop() {
|
||||||
|
u.connMutex.Lock()
|
||||||
|
defer u.connMutex.Unlock()
|
||||||
|
if u.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Update addrIndex to a new random value
|
||||||
|
u.addrIndex = rand.Intn(len(u.Addrs))
|
||||||
|
newConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr))
|
||||||
|
if err != nil {
|
||||||
|
// Could be temporary, just skip this hop
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We need to keep receiving packets from the previous connection,
|
||||||
|
// because otherwise there will be packet loss due to the time gap
|
||||||
|
// between we hop to a new port and the server acknowledges this change.
|
||||||
|
// So we do the following:
|
||||||
|
// Close prevConn,
|
||||||
|
// move currentConn to prevConn,
|
||||||
|
// set newConn as currentConn,
|
||||||
|
// start recvLoop on newConn.
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = u.prevConn.Close() // recvLoop for this conn will exit
|
||||||
|
}
|
||||||
|
u.prevConn = u.currentConn
|
||||||
|
u.currentConn = newConn
|
||||||
|
// Set buffer sizes if previously set
|
||||||
|
if u.readBufferSize > 0 {
|
||||||
|
_ = trySetReadBuffer(u.currentConn, u.readBufferSize)
|
||||||
|
}
|
||||||
|
if u.writeBufferSize > 0 {
|
||||||
|
_ = trySetWriteBuffer(u.currentConn, u.writeBufferSize)
|
||||||
|
}
|
||||||
|
go u.recvLoop(newConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case p := <-u.recvQueue:
|
||||||
|
if p.Err != nil {
|
||||||
|
return 0, nil, p.Err
|
||||||
|
}
|
||||||
|
// Currently we do not check whether the packet is from
|
||||||
|
// the server or not due to performance reasons.
|
||||||
|
n := copy(b, p.Buf[:p.N])
|
||||||
|
u.bufPool.Put(p.Buf)
|
||||||
|
return n, u.Addr, nil
|
||||||
|
case <-u.closeChan:
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
if u.closed {
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
// Skip the check for now, always write to the server,
|
||||||
|
// for the same reason as in ReadFrom.
|
||||||
|
return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) Close() error {
|
||||||
|
u.connMutex.Lock()
|
||||||
|
defer u.connMutex.Unlock()
|
||||||
|
if u.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Close prevConn and currentConn
|
||||||
|
// Close closeChan to unblock ReadFrom & hopLoop
|
||||||
|
// Set closed flag to true to prevent double close
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = u.prevConn.Close()
|
||||||
|
}
|
||||||
|
err := u.currentConn.Close()
|
||||||
|
close(u.closeChan)
|
||||||
|
u.closed = true
|
||||||
|
u.Addrs = nil // For GC
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) LocalAddr() net.Addr {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
return u.currentConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SetDeadline(t time.Time) error {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = u.prevConn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
return u.currentConn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = u.prevConn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
return u.currentConn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = u.prevConn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
return u.currentConn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDP-specific methods below
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SetReadBuffer(bytes int) error {
|
||||||
|
u.connMutex.Lock()
|
||||||
|
defer u.connMutex.Unlock()
|
||||||
|
u.readBufferSize = bytes
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = trySetReadBuffer(u.prevConn, bytes)
|
||||||
|
}
|
||||||
|
return trySetReadBuffer(u.currentConn, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SetWriteBuffer(bytes int) error {
|
||||||
|
u.connMutex.Lock()
|
||||||
|
defer u.connMutex.Unlock()
|
||||||
|
u.writeBufferSize = bytes
|
||||||
|
if u.prevConn != nil {
|
||||||
|
_ = trySetWriteBuffer(u.prevConn, bytes)
|
||||||
|
}
|
||||||
|
return trySetWriteBuffer(u.currentConn, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *udpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
u.connMutex.RLock()
|
||||||
|
defer u.connMutex.RUnlock()
|
||||||
|
sc, ok := u.currentConn.(syscall.Conn)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not supported")
|
||||||
|
}
|
||||||
|
return sc.SyscallConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySetReadBuffer(pc net.PacketConn, bytes int) error {
|
||||||
|
sc, ok := pc.(interface {
|
||||||
|
SetReadBuffer(bytes int) error
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
return sc.SetReadBuffer(bytes)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySetWriteBuffer(pc net.PacketConn, bytes int) error {
|
||||||
|
sc, ok := pc.(interface {
|
||||||
|
SetWriteBuffer(bytes int) error
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
return sc.SetWriteBuffer(bytes)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortUnion is a collection of multiple port ranges.
|
||||||
|
type PortUnion []PortRange
|
||||||
|
|
||||||
|
// PortRange represents a range of ports.
|
||||||
|
// Start and End are inclusive. [Start, End]
|
||||||
|
type PortRange struct {
|
||||||
|
Start, End uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePortUnion parses a string of comma-separated port ranges (or single ports) into a PortUnion.
|
||||||
|
// Returns nil if the input is invalid.
|
||||||
|
// The returned PortUnion is guaranteed to be normalized.
|
||||||
|
func ParsePortUnion(s string) PortUnion {
|
||||||
|
if s == "all" || s == "*" {
|
||||||
|
// Wildcard special case
|
||||||
|
return PortUnion{PortRange{0, 65535}}
|
||||||
|
}
|
||||||
|
var result PortUnion
|
||||||
|
portStrs := strings.Split(s, ",")
|
||||||
|
for _, portStr := range portStrs {
|
||||||
|
if strings.Contains(portStr, "-") {
|
||||||
|
// Port range
|
||||||
|
portRange := strings.Split(portStr, "-")
|
||||||
|
if len(portRange) != 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start, err := strconv.ParseUint(portRange[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
end, err := strconv.ParseUint(portRange[1], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if start > end {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
result = append(result, PortRange{uint16(start), uint16(end)})
|
||||||
|
} else {
|
||||||
|
// Single port
|
||||||
|
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = append(result, PortRange{uint16(port), uint16(port)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result.Normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize normalizes a PortUnion.
|
||||||
|
// No overlapping ranges, ranges are sorted from low to high.
|
||||||
|
func (u PortUnion) Normalize() PortUnion {
|
||||||
|
if len(u) == 0 {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
sort.Slice(u, func(i, j int) bool {
|
||||||
|
if u[i].Start == u[j].Start {
|
||||||
|
return u[i].End < u[j].End
|
||||||
|
}
|
||||||
|
return u[i].Start < u[j].Start
|
||||||
|
})
|
||||||
|
normalized := PortUnion{u[0]}
|
||||||
|
for _, current := range u[1:] {
|
||||||
|
last := &normalized[len(normalized)-1]
|
||||||
|
if uint32(current.Start) <= uint32(last.End)+1 {
|
||||||
|
if current.End > last.End {
|
||||||
|
last.End = current.End
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
normalized = append(normalized, current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ports returns all ports in the PortUnion as a slice.
|
||||||
|
func (u PortUnion) Ports() []uint16 {
|
||||||
|
var ports []uint16
|
||||||
|
for _, r := range u {
|
||||||
|
for i := uint32(r.Start); i <= uint32(r.End); i++ {
|
||||||
|
ports = append(ports, uint16(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the PortUnion contains the given port.
|
||||||
|
func (u PortUnion) Contains(port uint16) bool {
|
||||||
|
for _, r := range u {
|
||||||
|
if port >= r.Start && port <= r.End {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParsePortUnion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s string
|
||||||
|
want PortUnion
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
s: "",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all 1",
|
||||||
|
s: "all",
|
||||||
|
want: PortUnion{{0, 65535}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all 2",
|
||||||
|
s: "*",
|
||||||
|
want: PortUnion{{0, 65535}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single port",
|
||||||
|
s: "1234",
|
||||||
|
want: PortUnion{{1234, 1234}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports (unsorted)",
|
||||||
|
s: "5678,1234,9012",
|
||||||
|
want: PortUnion{{1234, 1234}, {5678, 5678}, {9012, 9012}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one range",
|
||||||
|
s: "1234-1240",
|
||||||
|
want: PortUnion{{1234, 1240}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one range (reversed)",
|
||||||
|
s: "1240-1234",
|
||||||
|
want: PortUnion{{1234, 1240}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports and ranges (reversed, unsorted, overlapping)",
|
||||||
|
s: "5678,1200-1236,9100-9012,1234-1240",
|
||||||
|
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping)",
|
||||||
|
s: "5678,1200-1236,65531-65535,65532-65534,9100-9012,1234-1240",
|
||||||
|
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping) 2",
|
||||||
|
s: "5678,1200-1236,65532-65535,65531-65534,9100-9012,1234-1240",
|
||||||
|
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 1",
|
||||||
|
s: "1234-",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 2",
|
||||||
|
s: "1234-ggez",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 3",
|
||||||
|
s: "233,",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 4",
|
||||||
|
s: "1234-1240-1250",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 5",
|
||||||
|
s: "-,,",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 6",
|
||||||
|
s: "http",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ParsePortUnion(tt.s); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ParsePortUnion() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortUnion_Ports(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pu PortUnion
|
||||||
|
want []uint16
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single port",
|
||||||
|
pu: PortUnion{{1234, 1234}},
|
||||||
|
want: []uint16{1234},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports",
|
||||||
|
pu: PortUnion{{1234, 1236}},
|
||||||
|
want: []uint16{1234, 1235, 1236},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports and ranges",
|
||||||
|
pu: PortUnion{{1234, 1236}, {5678, 5680}, {9000, 9002}},
|
||||||
|
want: []uint16{1234, 1235, 1236, 5678, 5679, 5680, 9000, 9001, 9002},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single port 65535",
|
||||||
|
pu: PortUnion{{65535, 65535}},
|
||||||
|
want: []uint16{65535},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "port range with 65535",
|
||||||
|
pu: PortUnion{{65530, 65535}},
|
||||||
|
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ports and ranges with 65535",
|
||||||
|
pu: PortUnion{{65530, 65535}, {1234, 1236}},
|
||||||
|
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535, 1234, 1235, 1236},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.pu.Ports(); !slices.Equal(got, tt.want) {
|
||||||
|
t.Errorf("PortUnion.Ports() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package internet
|
package internet
|
||||||
|
|
||||||
import "github.com/xtls/xray-core/common/net"
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||||
|
)
|
||||||
|
|
||||||
// MemoryStreamConfig is a parsed form of StreamConfig. It is used to reduce the number of Protobuf parses.
|
// MemoryStreamConfig is a parsed form of StreamConfig. It is used to reduce the number of Protobuf parses.
|
||||||
type MemoryStreamConfig struct {
|
type MemoryStreamConfig struct {
|
||||||
@@ -9,6 +12,8 @@ type MemoryStreamConfig struct {
|
|||||||
ProtocolSettings interface{}
|
ProtocolSettings interface{}
|
||||||
SecurityType string
|
SecurityType string
|
||||||
SecuritySettings interface{}
|
SecuritySettings interface{}
|
||||||
|
TcpmaskManager *finalmask.TcpmaskManager
|
||||||
|
UdpmaskManager *finalmask.UdpmaskManager
|
||||||
SocketSettings *SocketConfig
|
SocketSettings *SocketConfig
|
||||||
DownloadSettings *MemoryStreamConfig
|
DownloadSettings *MemoryStreamConfig
|
||||||
}
|
}
|
||||||
@@ -45,5 +50,29 @@ func ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {
|
|||||||
mss.SecuritySettings = ess
|
mss.SecuritySettings = ess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s != nil && len(s.Tcpmasks) > 0 {
|
||||||
|
var masks []finalmask.Tcpmask
|
||||||
|
for _, msg := range s.Tcpmasks {
|
||||||
|
instance, err := msg.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
masks = append(masks, instance.(finalmask.Tcpmask))
|
||||||
|
}
|
||||||
|
mss.TcpmaskManager = finalmask.NewTcpmaskManager(masks)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != nil && len(s.Udpmasks) > 0 {
|
||||||
|
var masks []finalmask.Udpmask
|
||||||
|
for _, msg := range s.Udpmasks {
|
||||||
|
instance, err := msg.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
masks = append(masks, instance.(finalmask.Udpmask))
|
||||||
|
}
|
||||||
|
mss.UdpmaskManager = finalmask.NewUdpmaskManager(masks)
|
||||||
|
}
|
||||||
|
|
||||||
return mss, nil
|
return mss, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/apernet/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/apernet/quic-go/http3"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/apernet/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/apernet/quic-go/http3"
|
||||||
goreality "github.com/xtls/reality"
|
goreality "github.com/xtls/reality"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
|||||||
@@ -195,6 +195,14 @@ func (c *PacketConnWrapper) SetWriteDeadline(t time.Time) error {
|
|||||||
return c.Conn.SetWriteDeadline(t)
|
return c.Conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
sc, ok := c.Conn.(syscall.Conn)
|
||||||
|
if !ok {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
return sc.SyscallConn()
|
||||||
|
}
|
||||||
|
|
||||||
type SystemDialerAdapter interface {
|
type SystemDialerAdapter interface {
|
||||||
Dial(network string, address string) (net.Conn, error)
|
Dial(network string, address string) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -281,63 +280,83 @@ func (c *Config) parseServerName() string {
|
|||||||
return c.ServerName
|
return c.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) {
|
||||||
if r.VerifyPeerCertInNames != nil {
|
// extract x509 certificates from rawCerts (verifiedChains will be nil if InsecureSkipVerify is true)
|
||||||
if len(r.VerifyPeerCertInNames) > 0 {
|
certs := make([]*x509.Certificate, len(rawCerts))
|
||||||
certs := make([]*x509.Certificate, len(rawCerts))
|
for i, asn1Data := range rawCerts {
|
||||||
for i, asn1Data := range rawCerts {
|
certs[i], _ = x509.ParseCertificate(asn1Data)
|
||||||
certs[i], _ = x509.ParseCertificate(asn1Data)
|
}
|
||||||
}
|
if len(certs) == 0 {
|
||||||
opts := x509.VerifyOptions{
|
return errors.New("unexpected certs")
|
||||||
Roots: r.RootCAs,
|
}
|
||||||
CurrentTime: time.Now(),
|
if certs[0].IsCA {
|
||||||
Intermediates: x509.NewCertPool(),
|
slices.Reverse(certs)
|
||||||
}
|
}
|
||||||
for _, cert := range certs[1:] {
|
|
||||||
opts.Intermediates.AddCert(cert)
|
// directly return success if pinned cert is leaf
|
||||||
}
|
// or replace RootCAs if pinned cert is CA (and can be used in VerifyPeerCertInNames)
|
||||||
for _, opts.DNSName = range r.VerifyPeerCertInNames {
|
CAs := r.RootCAs
|
||||||
if _, err := certs[0].Verify(opts); err == nil {
|
var verifyResult verifyResult
|
||||||
return nil
|
var verifiedCert *x509.Certificate
|
||||||
}
|
if r.PinnedPeerCertSha256 != nil {
|
||||||
}
|
verifyResult, verifiedCert = verifyChain(certs, r.PinnedPeerCertSha256)
|
||||||
}
|
switch verifyResult {
|
||||||
if r.PinnedPeerCertificateChainSha256 == nil {
|
case certNotFound:
|
||||||
return errors.New("peer cert is invalid.")
|
return errors.New("peer cert is unrecognized (againsts pinnedPeerCertSha256)")
|
||||||
|
case foundLeaf:
|
||||||
|
return nil
|
||||||
|
case foundCA:
|
||||||
|
CAs = x509.NewCertPool()
|
||||||
|
CAs.AddCert(verifiedCert)
|
||||||
|
default:
|
||||||
|
panic("impossible pinnedPeerCertSha256 verify result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.PinnedPeerCertificateChainSha256 != nil {
|
if r.VerifyPeerCertInNames != nil { // RAW's Dial() may make it empty but not nil
|
||||||
hashValue := GenerateCertChainHash(rawCerts)
|
opts := x509.VerifyOptions{
|
||||||
for _, v := range r.PinnedPeerCertificateChainSha256 {
|
Roots: CAs,
|
||||||
if hmac.Equal(hashValue, v) {
|
CurrentTime: time.Now(),
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
for _, cert := range certs[1:] {
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
for _, opts.DNSName = range r.VerifyPeerCertInNames {
|
||||||
|
if _, err := certs[0].Verify(opts); err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue))
|
if verifyResult == foundCA {
|
||||||
|
errors.New("peer cert is invalid (againsts pinned CA and verifyPeerCertInNames)")
|
||||||
|
}
|
||||||
|
return errors.New("peer cert is invalid (againsts root CAs and verifyPeerCertInNames)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.PinnedPeerCertificatePublicKeySha256 != nil {
|
if verifyResult == foundCA { // if found CA, we need to verify here
|
||||||
for _, v := range verifiedChains {
|
opts := x509.VerifyOptions{
|
||||||
for _, cert := range v {
|
Roots: CAs,
|
||||||
publicHash := GenerateCertPublicKeyHash(cert)
|
CurrentTime: time.Now(),
|
||||||
for _, c := range r.PinnedPeerCertificatePublicKeySha256 {
|
Intermediates: x509.NewCertPool(),
|
||||||
if hmac.Equal(publicHash, c) {
|
DNSName: r.Config.ServerName,
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return errors.New("peer public key is unrecognized.")
|
for _, cert := range certs[1:] {
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
if _, err := certs[0].Verify(opts); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("peer cert is invalid (againsts pinned CA and serverName)")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return nil // len(r.PinnedPeerCertSha256)==nil && len(r.VerifyPeerCertInNames)==nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RandCarrier struct {
|
type RandCarrier struct {
|
||||||
RootCAs *x509.CertPool
|
Config *tls.Config
|
||||||
VerifyPeerCertInNames []string
|
RootCAs *x509.CertPool
|
||||||
PinnedPeerCertificateChainSha256 [][]byte
|
VerifyPeerCertInNames []string
|
||||||
PinnedPeerCertificatePublicKeySha256 [][]byte
|
PinnedPeerCertSha256 [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RandCarrier) Read(p []byte) (n int, err error) {
|
func (r *RandCarrier) Read(p []byte) (n int, err error) {
|
||||||
@@ -362,10 +381,9 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
randCarrier := &RandCarrier{
|
randCarrier := &RandCarrier{
|
||||||
RootCAs: root,
|
RootCAs: root,
|
||||||
VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames),
|
VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames),
|
||||||
PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256,
|
PinnedPeerCertSha256: c.PinnedPeerCertSha256,
|
||||||
PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256,
|
|
||||||
}
|
}
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
Rand: randCarrier,
|
Rand: randCarrier,
|
||||||
@@ -376,11 +394,17 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
SessionTicketsDisabled: !c.EnableSessionResumption,
|
SessionTicketsDisabled: !c.EnableSessionResumption,
|
||||||
VerifyPeerCertificate: randCarrier.verifyPeerCert,
|
VerifyPeerCertificate: randCarrier.verifyPeerCert,
|
||||||
}
|
}
|
||||||
|
randCarrier.Config = config
|
||||||
if len(c.VerifyPeerCertInNames) > 0 {
|
if len(c.VerifyPeerCertInNames) > 0 {
|
||||||
config.InsecureSkipVerify = true
|
config.InsecureSkipVerify = true
|
||||||
} else {
|
} else {
|
||||||
randCarrier.VerifyPeerCertInNames = nil
|
randCarrier.VerifyPeerCertInNames = nil
|
||||||
}
|
}
|
||||||
|
if len(c.PinnedPeerCertSha256) > 0 {
|
||||||
|
config.InsecureSkipVerify = true
|
||||||
|
} else {
|
||||||
|
randCarrier.PinnedPeerCertSha256 = nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(config)
|
opt(config)
|
||||||
@@ -526,3 +550,27 @@ func ParseCurveName(curveNames []string) []tls.CurveID {
|
|||||||
func IsFromMitm(str string) bool {
|
func IsFromMitm(str string) bool {
|
||||||
return strings.ToLower(str) == "frommitm"
|
return strings.ToLower(str) == "frommitm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type verifyResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
certNotFound verifyResult = iota
|
||||||
|
foundLeaf
|
||||||
|
foundCA
|
||||||
|
)
|
||||||
|
|
||||||
|
func verifyChain(certs []*x509.Certificate, pinnedPeerCertSha256 [][]byte) (verifyResult, *x509.Certificate) {
|
||||||
|
for _, cert := range certs {
|
||||||
|
certHash := GenerateCertHash(cert)
|
||||||
|
for _, c := range pinnedPeerCertSha256 {
|
||||||
|
if hmac.Equal(certHash, c) {
|
||||||
|
if cert.IsCA {
|
||||||
|
return foundCA, cert
|
||||||
|
} else {
|
||||||
|
return foundLeaf, cert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return certNotFound, nil
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user