Merge pull request #2725 from v2fly/2725

merge fly
pull/2727/head
Kslr 4 years ago committed by GitHub
commit b613a9bcb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,46 @@
run:
modules-download-mode: vendor
skip-dirs:
- generated.*
- external
linters:
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exhaustive
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- noctx
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

@ -1,43 +0,0 @@
name: Release Docker Image
on:
push:
tags:
- v*
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout default branch
uses: actions/checkout@v2
- name: Install Buildx and QEMU
run: |
export DOCKER_BUILDKIT=1
docker build --platform=local -o . git://github.com/docker/buildx
mkdir -p ~/.docker/cli-plugins
mv buildx ~/.docker/cli-plugins/docker-buildx
docker run --rm --privileged multiarch/qemu-user-static:latest --reset -p yes --credential yes
docker buildx create --use --name build --node build --driver-opt network=host
- name: Login to Docker Hub
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Build and push Docker image
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_IMAGE_PLATFORM: linux/386,linux/amd64,linux/arm/v7,linux/arm64
run: |
DOCKER_IMAGE_NAME=$(echo $DOCKER_USERNAME | tr '[:upper:]' '[:lower:]')/v2fly-core
DOCKER_IMAGE_VERSION=${GITHUB_REF#refs/*/}
docker buildx build \
--platform "$DOCKER_IMAGE_PLATFORM" \
--output "type=image,push=true" \
--tag "$DOCKER_IMAGE_NAME":"$DOCKER_IMAGE_VERSION" \
--tag "$DOCKER_IMAGE_NAME":latest \
--file ./Dockerfile .

@ -26,16 +26,6 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go- restore-keys: ${{ runner.os }}-go-
- name: Show if need to format code
if: ${{ always() }}
run: |
filesNeedToFormat=$(go fmt ./...)
if [[ $filesNeedToFormat ]]; then
echo -e "\033[0;36m[Error] The following Go files need to be formatted:\033[0m"
echo -e "\033[0;31m$filesNeedToFormat\033[0m"
exit 1
fi
- name: Lint *.go files - name: Lint *.go files
if: ${{ always() }} if: ${{ always() }}
run: | run: |
@ -46,7 +36,7 @@ jobs:
- name: Lint other files - name: Lint other files
if: ${{ always() }} if: ${{ always() }}
uses: github/super-linter@v3.9.2 uses: github/super-linter@v3.9.4
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_ALL_CODEBASE: false VALIDATE_ALL_CODEBASE: false
@ -57,3 +47,13 @@ jobs:
VALIDATE_JSON: false VALIDATE_JSON: false
VALIDATE_MD: false VALIDATE_MD: false
VALIDATE_PROTOBUF: false VALIDATE_PROTOBUF: false
- name: Show if need to format code
if: ${{ always() }}
run: |
filesNeedToFormat=$(go fmt ./...)
if [[ $filesNeedToFormat ]]; then
echo -e "\033[0;36m[Error] The following Go files need to be formatted:\033[0m"
echo -e "\033[0;31m$filesNeedToFormat\033[0m"
exit 1
fi

@ -20,6 +20,7 @@ import (
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/features/policy" "v2ray.com/core/features/policy"
"v2ray.com/core/features/routing" "v2ray.com/core/features/routing"
routing_session "v2ray.com/core/features/routing/session"
"v2ray.com/core/features/stats" "v2ray.com/core/features/stats"
"v2ray.com/core/transport" "v2ray.com/core/transport"
"v2ray.com/core/transport/pipe" "v2ray.com/core/transport/pipe"
@ -265,7 +266,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
} }
if d.router != nil && !skipRoutePick { if d.router != nil && !skipRoutePick {
if tag, err := d.router.PickRoute(ctx); err == nil { if tag, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil {
if h := d.ohm.GetHandler(tag); h != nil { if h := d.ohm.GetHandler(tag); h != nil {
newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h handler = h

@ -10,10 +10,11 @@ import (
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/strmatcher" "v2ray.com/core/common/strmatcher"
"v2ray.com/core/features/routing"
) )
type Condition interface { type Condition interface {
Apply(ctx *Context) bool Apply(ctx routing.Context) bool
} }
type ConditionChan []Condition type ConditionChan []Condition
@ -28,7 +29,8 @@ func (v *ConditionChan) Add(cond Condition) *ConditionChan {
return v return v
} }
func (v *ConditionChan) Apply(ctx *Context) bool { // Apply applies all conditions registered in this chan.
func (v *ConditionChan) Apply(ctx routing.Context) bool {
for _, cond := range *v { for _, cond := range *v {
if !cond.Apply(ctx) { if !cond.Apply(ctx) {
return false return false
@ -85,36 +87,18 @@ func (m *DomainMatcher) ApplyDomain(domain string) bool {
return len(m.matchers.Match(domain)) > 0 return len(m.matchers.Match(domain)) > 0
} }
func (m *DomainMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { func (m *DomainMatcher) Apply(ctx routing.Context) bool {
domain := ctx.GetTargetDomain()
if len(domain) == 0 {
return false return false
} }
dest := ctx.Outbound.Target return m.ApplyDomain(domain)
if !dest.Address.Family().IsDomain() {
return false
}
return m.ApplyDomain(dest.Address.Domain())
}
func getIPsFromSource(ctx *Context) []net.IP {
if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {
return nil
}
dest := ctx.Inbound.Source
if dest.Address.Family().IsDomain() {
return nil
}
return []net.IP{dest.Address.IP()}
}
func getIPsFromTarget(ctx *Context) []net.IP {
return ctx.GetTargetIPs()
} }
type MultiGeoIPMatcher struct { type MultiGeoIPMatcher struct {
matchers []*GeoIPMatcher matchers []*GeoIPMatcher
ipFunc func(*Context) []net.IP onSource bool
} }
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) { func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
@ -129,20 +113,20 @@ func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, e
matcher := &MultiGeoIPMatcher{ matcher := &MultiGeoIPMatcher{
matchers: matchers, matchers: matchers,
} onSource: onSource,
if onSource {
matcher.ipFunc = getIPsFromSource
} else {
matcher.ipFunc = getIPsFromTarget
} }
return matcher, nil return matcher, nil
} }
func (m *MultiGeoIPMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
ips := m.ipFunc(ctx) func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
var ips []net.IP
if m.onSource {
ips = ctx.GetSourceIPs()
} else {
ips = ctx.GetTargetIPs()
}
for _, ip := range ips { for _, ip := range ips {
for _, matcher := range m.matchers { for _, matcher := range m.matchers {
if matcher.Match(ip) { if matcher.Match(ip) {
@ -166,20 +150,13 @@ func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher {
} }
} }
func (v *PortMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
var port net.Port func (v *PortMatcher) Apply(ctx routing.Context) bool {
if v.onSource { if v.onSource {
if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() { return v.port.Contains(ctx.GetSourcePort())
return false
}
port = ctx.Inbound.Source.Port
} else { } else {
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { return v.port.Contains(ctx.GetTargetPort())
return false
}
port = ctx.Outbound.Target.Port
} }
return v.port.Contains(port)
} }
type NetworkMatcher struct { type NetworkMatcher struct {
@ -194,11 +171,9 @@ func NewNetworkMatcher(network []net.Network) NetworkMatcher {
return matcher return matcher
} }
func (v NetworkMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { func (v NetworkMatcher) Apply(ctx routing.Context) bool {
return false return v.list[int(ctx.GetNetwork())]
}
return v.list[int(ctx.Outbound.Target.Network)]
} }
type UserMatcher struct { type UserMatcher struct {
@ -217,17 +192,14 @@ func NewUserMatcher(users []string) *UserMatcher {
} }
} }
func (v *UserMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Inbound == nil { func (v *UserMatcher) Apply(ctx routing.Context) bool {
return false user := ctx.GetUser()
} if len(user) == 0 {
user := ctx.Inbound.User
if user == nil {
return false return false
} }
for _, u := range v.user { for _, u := range v.user {
if u == user.Email { if u == user {
return true return true
} }
} }
@ -250,11 +222,12 @@ func NewInboundTagMatcher(tags []string) *InboundTagMatcher {
} }
} }
func (v *InboundTagMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Inbound == nil || len(ctx.Inbound.Tag) == 0 { func (v *InboundTagMatcher) Apply(ctx routing.Context) bool {
tag := ctx.GetInboundTag()
if len(tag) == 0 {
return false return false
} }
tag := ctx.Inbound.Tag
for _, t := range v.tags { for _, t := range v.tags {
if t == tag { if t == tag {
return true return true
@ -281,18 +254,17 @@ func NewProtocolMatcher(protocols []string) *ProtocolMatcher {
} }
} }
func (m *ProtocolMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Content == nil { func (m *ProtocolMatcher) Apply(ctx routing.Context) bool {
protocol := ctx.GetProtocol()
if len(protocol) == 0 {
return false return false
} }
protocol := ctx.Content.Protocol
for _, p := range m.protocols { for _, p := range m.protocols {
if strings.HasPrefix(protocol, p) { if strings.HasPrefix(protocol, p) {
return true return true
} }
} }
return false return false
} }
@ -343,9 +315,11 @@ func (m *AttributeMatcher) Match(attrs map[string]interface{}) bool {
return satisfied != nil && bool(satisfied.Truth()) return satisfied != nil && bool(satisfied.Truth())
} }
func (m *AttributeMatcher) Apply(ctx *Context) bool { // Apply implements Condition.
if ctx.Content == nil { func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
attributes := ctx.GetAttributes()
if attributes == nil {
return false return false
} }
return m.Match(ctx.Content.Attributes) return m.Match(attributes)
} }

@ -17,8 +17,12 @@ func init() {
wd, err := os.Getwd() wd, err := os.Getwd()
common.Must(err) common.Must(err)
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
}
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
}
} }
func TestGeoIPMatcherContainer(t *testing.T) { func TestGeoIPMatcherContainer(t *testing.T) {

@ -17,27 +17,41 @@ import (
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/protocol/http" "v2ray.com/core/common/protocol/http"
"v2ray.com/core/common/session" "v2ray.com/core/common/session"
"v2ray.com/core/features/routing"
routing_session "v2ray.com/core/features/routing/session"
) )
func init() { func init() {
wd, err := os.Getwd() wd, err := os.Getwd()
common.Must(err) common.Must(err)
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat"))) common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
}
if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
}
}
func withBackground() routing.Context {
return &routing_session.Context{}
}
func withOutbound(outbound *session.Outbound) routing.Context {
return &routing_session.Context{Outbound: outbound}
} }
func withOutbound(outbound *session.Outbound) *Context { func withInbound(inbound *session.Inbound) routing.Context {
return &Context{Outbound: outbound} return &routing_session.Context{Inbound: inbound}
} }
func withInbound(inbound *session.Inbound) *Context { func withContent(content *session.Content) routing.Context {
return &Context{Inbound: inbound} return &routing_session.Context{Content: content}
} }
func TestRoutingRule(t *testing.T) { func TestRoutingRule(t *testing.T) {
type ruleTest struct { type ruleTest struct {
input *Context input routing.Context
output bool output bool
} }
@ -88,7 +102,7 @@ func TestRoutingRule(t *testing.T) {
output: false, output: false,
}, },
{ {
input: &Context{}, input: withBackground(),
output: false, output: false,
}, },
}, },
@ -124,7 +138,7 @@ func TestRoutingRule(t *testing.T) {
output: true, output: true,
}, },
{ {
input: &Context{}, input: withBackground(),
output: false, output: false,
}, },
}, },
@ -164,7 +178,7 @@ func TestRoutingRule(t *testing.T) {
output: true, output: true,
}, },
{ {
input: &Context{}, input: withBackground(),
output: false, output: false,
}, },
}, },
@ -205,7 +219,7 @@ func TestRoutingRule(t *testing.T) {
output: false, output: false,
}, },
{ {
input: &Context{}, input: withBackground(),
output: false, output: false,
}, },
}, },
@ -216,7 +230,7 @@ func TestRoutingRule(t *testing.T) {
}, },
test: []ruleTest{ test: []ruleTest{
{ {
input: &Context{Content: &session.Content{Protocol: (&http.SniffHeader{}).Protocol()}}, input: withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}),
output: true, output: true,
}, },
}, },
@ -299,7 +313,7 @@ func TestRoutingRule(t *testing.T) {
}, },
test: []ruleTest{ test: []ruleTest{
{ {
input: &Context{Content: &session.Content{Protocol: "http/1.1", Attributes: map[string]interface{}{":path": "/test/1"}}}, input: withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]interface{}{":path": "/test/1"}}),
output: true, output: true,
}, },
}, },

@ -5,6 +5,7 @@ package router
import ( import (
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/features/routing"
) )
// CIDRList is an alias of []*CIDR to provide sort.Interface. // CIDRList is an alias of []*CIDR to provide sort.Interface.
@ -59,7 +60,8 @@ func (r *Rule) GetTag() (string, error) {
return r.Tag, nil return r.Tag, nil
} }
func (r *Rule) Apply(ctx *Context) bool { // Apply checks rule matching of current routing context.
func (r *Rule) Apply(ctx routing.Context) bool {
return r.Condition.Apply(ctx) return r.Condition.Apply(ctx)
} }

@ -10,7 +10,6 @@ import (
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/session"
"v2ray.com/core/features/dns" "v2ray.com/core/features/dns"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/features/routing" "v2ray.com/core/features/routing"
@ -74,7 +73,8 @@ func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error
return nil return nil
} }
func (r *Router) PickRoute(ctx context.Context) (string, error) { // PickRoute implements routing.Router.
func (r *Router) PickRoute(ctx routing.Context) (string, error) {
rule, err := r.pickRouteInternal(ctx) rule, err := r.pickRouteInternal(ctx)
if err != nil { if err != nil {
return "", err return "", err
@ -82,37 +82,26 @@ func (r *Router) PickRoute(ctx context.Context) (string, error) {
return rule.GetTag() return rule.GetTag()
} }
func isDomainOutbound(outbound *session.Outbound) bool { func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, error) {
return outbound != nil && outbound.Target.IsValid() && outbound.Target.Address.Family().IsDomain()
}
// PickRoute implements routing.Router.
func (r *Router) pickRouteInternal(ctx context.Context) (*Rule, error) {
sessionContext := &Context{
Inbound: session.InboundFromContext(ctx),
Outbound: session.OutboundFromContext(ctx),
Content: session.ContentFromContext(ctx),
}
if r.domainStrategy == Config_IpOnDemand { if r.domainStrategy == Config_IpOnDemand {
sessionContext.dnsClient = r.dns ctx = ContextWithDNSClient(ctx, r.dns)
} }
for _, rule := range r.rules { for _, rule := range r.rules {
if rule.Apply(sessionContext) { if rule.Apply(ctx) {
return rule, nil return rule, nil
} }
} }
if r.domainStrategy != Config_IpIfNonMatch || !isDomainOutbound(sessionContext.Outbound) { if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 {
return nil, common.ErrNoClue return nil, common.ErrNoClue
} }
sessionContext.dnsClient = r.dns ctx = ContextWithDNSClient(ctx, r.dns)
// Try applying rules again if we have IPs. // Try applying rules again if we have IPs.
for _, rule := range r.rules { for _, rule := range r.rules {
if rule.Apply(sessionContext) { if rule.Apply(ctx) {
return rule, nil return rule, nil
} }
} }
@ -135,32 +124,30 @@ func (*Router) Type() interface{} {
return routing.RouterType() return routing.RouterType()
} }
type Context struct { // ContextWithDNSClient creates a new routing context with domain resolving capability. Resolved domain IPs can be retrieved by GetTargetIPs().
Inbound *session.Inbound func ContextWithDNSClient(ctx routing.Context, client dns.Client) routing.Context {
Outbound *session.Outbound return &resolvableContext{Context: ctx, dnsClient: client}
Content *session.Content
dnsClient dns.Client
} }
func (c *Context) GetTargetIPs() []net.IP { type resolvableContext struct {
if c.Outbound == nil || !c.Outbound.Target.IsValid() { routing.Context
return nil dnsClient dns.Client
} resolvedIPs []net.IP
}
if c.Outbound.Target.Address.Family().IsIP() { func (ctx *resolvableContext) GetTargetIPs() []net.IP {
return []net.IP{c.Outbound.Target.Address.IP()} if ips := ctx.Context.GetTargetIPs(); len(ips) != 0 {
return ips
} }
if len(c.Outbound.ResolvedIPs) > 0 { if len(ctx.resolvedIPs) > 0 {
return c.Outbound.ResolvedIPs return ctx.resolvedIPs
} }
if c.dnsClient != nil { if domain := ctx.GetTargetDomain(); len(domain) != 0 {
domain := c.Outbound.Target.Address.Domain() ips, err := ctx.dnsClient.LookupIP(domain)
ips, err := c.dnsClient.LookupIP(domain)
if err == nil { if err == nil {
c.Outbound.ResolvedIPs = ips ctx.resolvedIPs = ips
return ips return ips
} }
newError("resolve ip for ", domain).Base(err).WriteToLog() newError("resolve ip for ", domain).Base(err).WriteToLog()

@ -10,6 +10,7 @@ import (
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/session" "v2ray.com/core/common/session"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
routing_session "v2ray.com/core/features/routing/session"
"v2ray.com/core/testing/mocks" "v2ray.com/core/testing/mocks"
) )
@ -44,7 +45,7 @@ func TestSimpleRouter(t *testing.T) {
})) }))
ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
common.Must(err) common.Must(err)
if tag != "test" { if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag) t.Error("expect tag 'test', bug actually ", tag)
@ -85,7 +86,7 @@ func TestSimpleBalancer(t *testing.T) {
})) }))
ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
common.Must(err) common.Must(err)
if tag != "test" { if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag) t.Error("expect tag 'test', bug actually ", tag)
@ -120,7 +121,7 @@ func TestIPOnDemand(t *testing.T) {
common.Must(r.Init(config, mockDns, nil)) common.Must(r.Init(config, mockDns, nil))
ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
common.Must(err) common.Must(err)
if tag != "test" { if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag) t.Error("expect tag 'test', bug actually ", tag)
@ -155,7 +156,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
common.Must(r.Init(config, mockDns, nil)) common.Must(r.Init(config, mockDns, nil))
ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
common.Must(err) common.Must(err)
if tag != "test" { if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag) t.Error("expect tag 'test', bug actually ", tag)
@ -189,7 +190,7 @@ func TestIPIfNonMatchIP(t *testing.T) {
common.Must(r.Init(config, mockDns, nil)) common.Must(r.Init(config, mockDns, nil))
ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)}) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)})
tag, err := r.PickRoute(ctx) tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
common.Must(err) common.Must(err)
if tag != "test" { if tag != "test" {
t.Error("expect tag 'test', bug actually ", tag) t.Error("expect tag 'test', bug actually ", tag)

@ -63,7 +63,7 @@ func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest
return nil, newError("QueryStats only works its own stats.Manager.") return nil, newError("QueryStats only works its own stats.Manager.")
} }
manager.Visit(func(name string, c feature_stats.Counter) bool { manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
if matcher.Match(name) { if matcher.Match(name) {
var value int64 var value int64
if request.Reset_ { if request.Reset_ {

@ -8,6 +8,7 @@ import (
"context" "context"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"v2ray.com/core/features/stats" "v2ray.com/core/features/stats"
) )
@ -32,15 +33,76 @@ func (c *Counter) Add(delta int64) int64 {
return atomic.AddInt64(&c.value, delta) return atomic.AddInt64(&c.value, delta)
} }
// Channel is an implementation of stats.Channel
type Channel struct {
channel chan interface{}
subscribers []chan interface{}
access sync.RWMutex
}
// Channel implements stats.Channel
func (c *Channel) Channel() chan interface{} {
return c.channel
}
// Subscribers implements stats.Channel
func (c *Channel) Subscribers() []chan interface{} {
c.access.RLock()
defer c.access.RUnlock()
return c.subscribers
}
// Subscribe implements stats.Channel
func (c *Channel) Subscribe() chan interface{} {
c.access.Lock()
defer c.access.Unlock()
ch := make(chan interface{})
c.subscribers = append(c.subscribers, ch)
return ch
}
// Unsubscribe implements stats.Channel
func (c *Channel) Unsubscribe(ch chan interface{}) {
c.access.Lock()
defer c.access.Unlock()
for i, s := range c.subscribers {
if s == ch {
// Copy to new memory block to prevent modifying original data
subscribers := make([]chan interface{}, len(c.subscribers)-1)
copy(subscribers[:i], c.subscribers[:i])
copy(subscribers[i:], c.subscribers[i+1:])
c.subscribers = subscribers
return
}
}
}
// Start starts the channel for listening to messsages
func (c *Channel) Start() {
for message := range c.Channel() {
subscribers := c.Subscribers() // Store a copy of slice value for concurrency safety
for _, sub := range subscribers {
select {
case sub <- message: // Successfully sent message
case <-time.After(100 * time.Millisecond):
c.Unsubscribe(sub) // Remove timeout subscriber
close(sub) // Actively close subscriber as notification
}
}
}
}
// Manager is an implementation of stats.Manager. // Manager is an implementation of stats.Manager.
type Manager struct { type Manager struct {
access sync.RWMutex access sync.RWMutex
counters map[string]*Counter counters map[string]*Counter
channels map[string]*Channel
} }
func NewManager(ctx context.Context, config *Config) (*Manager, error) { func NewManager(ctx context.Context, config *Config) (*Manager, error) {
m := &Manager{ m := &Manager{
counters: make(map[string]*Counter), counters: make(map[string]*Counter),
channels: make(map[string]*Channel),
} }
return m, nil return m, nil
@ -50,6 +112,7 @@ func (*Manager) Type() interface{} {
return stats.ManagerType() return stats.ManagerType()
} }
// RegisterCounter implements stats.Manager.
func (m *Manager) RegisterCounter(name string) (stats.Counter, error) { func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
m.access.Lock() m.access.Lock()
defer m.access.Unlock() defer m.access.Unlock()
@ -63,18 +126,7 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
return c, nil return c, nil
} }
func (m *Manager) UnregisterCounter(name string) error { // GetCounter implements stats.Manager.
m.access.Lock()
defer m.access.Unlock()
if _, found := m.counters[name]; !found {
return newError("Counter ", name, " was not found.")
}
newError("remove counter ", name).AtDebug().WriteToLog()
delete(m.counters, name)
return nil
}
func (m *Manager) GetCounter(name string) stats.Counter { func (m *Manager) GetCounter(name string) stats.Counter {
m.access.RLock() m.access.RLock()
defer m.access.RUnlock() defer m.access.RUnlock()
@ -85,7 +137,8 @@ func (m *Manager) GetCounter(name string) stats.Counter {
return nil return nil
} }
func (m *Manager) Visit(visitor func(string, stats.Counter) bool) { // VisitCounters calls visitor function on all managed counters.
func (m *Manager) VisitCounters(visitor func(string, stats.Counter) bool) {
m.access.RLock() m.access.RLock()
defer m.access.RUnlock() defer m.access.RUnlock()
@ -96,6 +149,32 @@ func (m *Manager) Visit(visitor func(string, stats.Counter) bool) {
} }
} }
// RegisterChannel implements stats.Manager.
func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
m.access.Lock()
defer m.access.Unlock()
if _, found := m.channels[name]; found {
return nil, newError("Channel ", name, " already registered.")
}
newError("create new channel ", name).AtDebug().WriteToLog()
c := &Channel{channel: make(chan interface{})}
m.channels[name] = c
go c.Start()
return c, nil
}
// GetChannel implements stats.Manager.
func (m *Manager) GetChannel(name string) stats.Channel {
m.access.RLock()
defer m.access.RUnlock()
if c, found := m.channels[name]; found {
return c
}
return nil
}
// Start implements common.Runnable. // Start implements common.Runnable.
func (m *Manager) Start() error { func (m *Manager) Start() error {
return nil return nil
@ -105,3 +184,4 @@ func (m *Manager) Start() error {
func (m *Manager) Close() error { func (m *Manager) Close() error {
return nil return nil
} }

@ -2,14 +2,16 @@ package stats_test
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time"
. "v2ray.com/core/app/stats" . "v2ray.com/core/app/stats"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/features/stats" "v2ray.com/core/features/stats"
) )
func TestInternface(t *testing.T) { func TestInterface(t *testing.T) {
_ = (stats.Manager)(new(Manager)) _ = (stats.Manager)(new(Manager))
} }
@ -33,3 +35,317 @@ func TestStatsCounter(t *testing.T) {
t.Fatal("unexpected Value() return: ", v, ", wanted ", 0) t.Fatal("unexpected Value() return: ", v, ", wanted ", 0)
} }
} }
func TestStatsChannel(t *testing.T) {
raw, err := common.CreateObject(context.Background(), &Config{})
common.Must(err)
m := raw.(stats.Manager)
c, err := m.RegisterChannel("test.channel")
common.Must(err)
source := c.Channel()
a := c.Subscribe()
b := c.Subscribe()
defer c.Unsubscribe(a)
defer c.Unsubscribe(b)
stopCh := make(chan struct{})
errCh := make(chan string)
go func() {
source <- 1
source <- 2
source <- "3"
source <- []int{4}
source <- nil // Dummy messsage with no subscriber receiving
select {
case source <- nil: // Source should be blocked here, for last message was not cleared
errCh <- fmt.Sprint("unexpected non-blocked source")
default:
close(stopCh)
}
}()
go func() {
if v, ok := (<-a).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
if v, ok := (<-a).(int); !ok || v != 2 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2)
}
if v, ok := (<-a).(string); !ok || v != "3" {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3")
}
if v, ok := (<-a).([]int); !ok || v[0] != 4 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4})
}
}()
go func() {
if v, ok := (<-b).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
if v, ok := (<-b).(int); !ok || v != 2 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2)
}
if v, ok := (<-b).(string); !ok || v != "3" {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3")
}
if v, ok := (<-b).([]int); !ok || v[0] != 4 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4})
}
}()
select {
case <-time.After(2 * time.Second):
t.Fatal("Test timeout after 2s")
case e := <-errCh:
t.Fatal(e)
case <-stopCh:
}
}
func TestStatsChannelUnsubcribe(t *testing.T) {
raw, err := common.CreateObject(context.Background(), &Config{})
common.Must(err)
m := raw.(stats.Manager)
c, err := m.RegisterChannel("test.channel")
common.Must(err)
source := c.Channel()
a := c.Subscribe()
b := c.Subscribe()
defer c.Unsubscribe(a)
pauseCh := make(chan struct{})
stopCh := make(chan struct{})
errCh := make(chan string)
{
var aSet, bSet bool
for _, s := range c.Subscribers() {
if s == a {
aSet = true
}
if s == b {
bSet = true
}
}
if !(aSet && bSet) {
t.Fatal("unexpected subscribers: ", c.Subscribers())
}
}
go func() {
source <- 1
<-pauseCh // Wait for `b` goroutine to resume sending message
source <- 2
}()
go func() {
if v, ok := (<-a).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
if v, ok := (<-a).(int); !ok || v != 2 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2)
}
}()
go func() {
if v, ok := (<-b).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
// Unsubscribe `b` while `source`'s messaging is paused
c.Unsubscribe(b)
{ // Test `b` is not in subscribers
var aSet, bSet bool
for _, s := range c.Subscribers() {
if s == a {
aSet = true
}
if s == b {
bSet = true
}
}
if !(aSet && !bSet) {
errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers())
}
}
// Resume `source`'s progress
close(pauseCh)
// Test `b` is neither closed nor able to receive any data
select {
case v, ok := <-b:
if ok {
errCh <- fmt.Sprint("unexpected data received: ", v)
} else {
errCh <- fmt.Sprint("unexpected closed channel: ", b)
}
default:
}
close(stopCh)
}()
select {
case <-time.After(2 * time.Second):
t.Fatal("Test timeout after 2s")
case e := <-errCh:
t.Fatal(e)
case <-stopCh:
}
}
func TestStatsChannelTimeout(t *testing.T) {
raw, err := common.CreateObject(context.Background(), &Config{})
common.Must(err)
m := raw.(stats.Manager)
c, err := m.RegisterChannel("test.channel")
common.Must(err)
source := c.Channel()
a := c.Subscribe()
b := c.Subscribe()
defer c.Unsubscribe(a)
defer c.Unsubscribe(b)
stopCh := make(chan struct{})
errCh := make(chan string)
go func() {
source <- 1
source <- 2
}()
go func() {
if v, ok := (<-a).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
if v, ok := (<-a).(int); !ok || v != 2 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2)
}
{ // Test `b` is still in subscribers yet (because `a` receives 2 first)
var aSet, bSet bool
for _, s := range c.Subscribers() {
if s == a {
aSet = true
}
if s == b {
bSet = true
}
}
if !(aSet && bSet) {
errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers())
}
}
}()
go func() {
if v, ok := (<-b).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
// Block `b` channel for a time longer than `source`'s timeout
<-time.After(150 * time.Millisecond)
{ // Test `b` has been unsubscribed by source
var aSet, bSet bool
for _, s := range c.Subscribers() {
if s == a {
aSet = true
}
if s == b {
bSet = true
}
}
if !(aSet && !bSet) {
errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers())
}
}
select { // Test `b` has been closed by source
case v, ok := <-b:
if ok {
errCh <- fmt.Sprint("unexpected data received: ", v)
}
default:
}
close(stopCh)
}()
select {
case <-time.After(2 * time.Second):
t.Fatal("Test timeout after 2s")
case e := <-errCh:
t.Fatal(e)
case <-stopCh:
}
}
func TestStatsChannelConcurrency(t *testing.T) {
raw, err := common.CreateObject(context.Background(), &Config{})
common.Must(err)
m := raw.(stats.Manager)
c, err := m.RegisterChannel("test.channel")
common.Must(err)
source := c.Channel()
a := c.Subscribe()
b := c.Subscribe()
defer c.Unsubscribe(a)
stopCh := make(chan struct{})
errCh := make(chan string)
go func() {
source <- 1
source <- 2
}()
go func() {
if v, ok := (<-a).(int); !ok || v != 1 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1)
}
if v, ok := (<-a).(int); !ok || v != 2 {
errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2)
}
}()
go func() {
// Block `b` for a time shorter than `source`'s timeout
// So as to ensure source channel is trying to send message to `b`.
<-time.After(25 * time.Millisecond)
// This causes concurrency scenario: unsubscribe `b` while trying to send message to it
c.Unsubscribe(b)
// Test `b` is not closed and can still receive data 1:
// Because unsubscribe won't affect the ongoing process of sending message.
select {
case v, ok := <-b:
if v1, ok1 := v.(int); !(ok && ok1 && v1 == 1) {
errCh <- fmt.Sprint("unexpected failure in receiving data: ", 1)
}
default:
errCh <- fmt.Sprint("unexpected block from receiving data: ", 1)
}
// Test `b` is not closed but cannot receive data 2:
// Becuase in a new round of messaging, `b` has been unsubscribed.
select {
case v, ok := <-b:
if ok {
errCh <- fmt.Sprint("unexpected receving: ", v)
} else {
errCh <- fmt.Sprint("unexpected closing of channel")
}
default:
}
close(stopCh)
}()
select {
case <-time.After(2 * time.Second):
t.Fatal("Test timeout after 2s")
case e := <-errCh:
t.Fatal(e)
case <-stopCh:
}
}

@ -20,7 +20,7 @@ steps:
- checkout: self - checkout: self
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.14.7' version: '1.15.1'
- script: | - script: |
mkdir triggersrc mkdir triggersrc
ls -I "triggersrc" | xargs cp -rf -t triggersrc ls -I "triggersrc" | xargs cp -rf -t triggersrc

@ -51,8 +51,6 @@ type Outbound struct {
Target net.Destination Target net.Destination
// Gateway address // Gateway address
Gateway net.Address Gateway net.Address
// ResolvedIPs is the resolved IP addresses, if the Targe is a domain address.
ResolvedIPs []net.IP
} }
type SniffingRequest struct { type SniffingRequest struct {

@ -0,0 +1,40 @@
package routing
import (
"v2ray.com/core/common/net"
)
// Context is a feature to store connection information for routing.
//
// v2ray:api:beta
type Context interface {
// GetInboundTag returns the tag of the inbound the connection was from.
GetInboundTag() string
// GetSourcesIPs returns the source IPs bound to the connection.
GetSourceIPs() []net.IP
// GetSourcePort returns the source port of the connection.
GetSourcePort() net.Port
// GetTargetIPs returns the target IP of the connection or resolved IPs of target domain.
GetTargetIPs() []net.IP
// GetTargetPort returns the target port of the connection.
GetTargetPort() net.Port
// GetTargetDomain returns the target domain of the connection, if exists.
GetTargetDomain() string
// GetNetwork returns the network type of the connection.
GetNetwork() net.Network
// GetProtocol returns the protocol from the connection content, if sniffed out.
GetProtocol() string
// GetUser returns the user email from the connection content, if exists.
GetUser() string
// GetAttributes returns extra attributes from the conneciont content.
GetAttributes() map[string]interface{}
}

@ -1,20 +1,18 @@
package routing package routing
import ( import (
"context"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/features" "v2ray.com/core/features"
) )
// Router is a feature to choose an outbound tag for the given request. // Router is a feature to choose an outbound tag for the given request.
// //
// v2ray:api:stable // v2ray:api:beta
type Router interface { type Router interface {
features.Feature features.Feature
// PickRoute returns a tag of an OutboundHandler based on the given context. // PickRoute returns a tag of an OutboundHandler based on the given context.
PickRoute(ctx context.Context) (string, error) PickRoute(ctx Context) (string, error)
} }
// RouterType return the type of Router interface. Can be used to implement common.HasType. // RouterType return the type of Router interface. Can be used to implement common.HasType.
@ -33,7 +31,7 @@ func (DefaultRouter) Type() interface{} {
} }
// PickRoute implements Router. // PickRoute implements Router.
func (DefaultRouter) PickRoute(ctx context.Context) (string, error) { func (DefaultRouter) PickRoute(ctx Context) (string, error) {
return "", common.ErrNoClue return "", common.ErrNoClue
} }

@ -0,0 +1,119 @@
package session
import (
"context"
"v2ray.com/core/common/net"
"v2ray.com/core/common/session"
"v2ray.com/core/features/routing"
)
// Context is an implementation of routing.Context, which is a wrapper of context.context with session info.
type Context struct {
Inbound *session.Inbound
Outbound *session.Outbound
Content *session.Content
}
// GetInboundTag implements routing.Context.
func (ctx *Context) GetInboundTag() string {
if ctx.Inbound == nil {
return ""
}
return ctx.Inbound.Tag
}
// GetSourceIPs implements routing.Context.
func (ctx *Context) GetSourceIPs() []net.IP {
if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {
return nil
}
dest := ctx.Inbound.Source
if dest.Address.Family().IsDomain() {
return nil
}
return []net.IP{dest.Address.IP()}
}
// GetSourcePort implements routing.Context.
func (ctx *Context) GetSourcePort() net.Port {
if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {
return 0
}
return ctx.Inbound.Source.Port
}
// GetTargetIPs implements routing.Context.
func (ctx *Context) GetTargetIPs() []net.IP {
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
return nil
}
if ctx.Outbound.Target.Address.Family().IsIP() {
return []net.IP{ctx.Outbound.Target.Address.IP()}
}
return nil
}
// GetTargetPort implements routing.Context.
func (ctx *Context) GetTargetPort() net.Port {
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
return 0
}
return ctx.Outbound.Target.Port
}
// GetTargetDomain implements routing.Context.
func (ctx *Context) GetTargetDomain() string {
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
return ""
}
dest := ctx.Outbound.Target
if !dest.Address.Family().IsDomain() {
return ""
}
return dest.Address.Domain()
}
// GetNetwork implements routing.Context.
func (ctx *Context) GetNetwork() net.Network {
if ctx.Outbound == nil {
return net.Network_Unknown
}
return ctx.Outbound.Target.Network
}
// GetProtocol implements routing.Context.
func (ctx *Context) GetProtocol() string {
if ctx.Content == nil {
return ""
}
return ctx.Content.Protocol
}
// GetUser implements routing.Context.
func (ctx *Context) GetUser() string {
if ctx.Inbound == nil {
return ""
}
return ctx.Inbound.User.Email
}
// GetAttributes implements routing.Context.
func (ctx *Context) GetAttributes() map[string]interface{} {
if ctx.Content == nil {
return nil
}
return ctx.Content.Attributes
}
// AsRoutingContext creates a context from context.context with session info.
func AsRoutingContext(ctx context.Context) routing.Context {
return &Context{
Inbound: session.InboundFromContext(ctx),
Outbound: session.OutboundFromContext(ctx),
Content: session.ContentFromContext(ctx),
}
}

@ -16,6 +16,20 @@ type Counter interface {
Add(int64) int64 Add(int64) int64
} }
// Channel is the interface for stats channel
//
// v2ray:api:stable
type Channel interface {
// Channel returns the underlying go channel.
Channel() chan interface{}
// SubscriberCount returns the number of the subscribers.
Subscribers() []chan interface{}
// Subscribe registers for listening to channel stream and returns a new listener channel.
Subscribe() chan interface{}
// Unsubscribe unregisters a listener channel from current Channel object.
Unsubscribe(chan interface{})
}
// Manager is the interface for stats manager. // Manager is the interface for stats manager.
// //
// v2ray:api:stable // v2ray:api:stable
@ -27,6 +41,11 @@ type Manager interface {
UnregisterCounter(string) error UnregisterCounter(string) error
// GetCounter returns a counter by its identifier. // GetCounter returns a counter by its identifier.
GetCounter(string) Counter GetCounter(string) Counter
// RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels.
RegisterChannel(string) (Channel, error)
// GetChannel returns a channel by its identifier.
GetChannel(string) Channel
} }
// GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter. // GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter.
@ -39,6 +58,16 @@ func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
return m.RegisterCounter(name) return m.RegisterCounter(name)
} }
// GetOrRegisterChannel tries to get the StatChannel first. If not exist, it then tries to create a new channel.
func GetOrRegisterChannel(m Manager, name string) (Channel, error) {
channel := m.GetChannel(name)
if channel != nil {
return channel, nil
}
return m.RegisterChannel(name)
}
// ManagerType returns the type of Manager interface. Can be used to implement common.HasType. // ManagerType returns the type of Manager interface. Can be used to implement common.HasType.
// //
// v2ray:api:stable // v2ray:api:stable
@ -69,6 +98,16 @@ func (NoopManager) GetCounter(string) Counter {
return nil return nil
} }
// RegisterChannel implements Manager.
func (NoopManager) RegisterChannel(string) (Channel, error) {
return nil, newError("not implemented")
}
// GetChannel implements Manager.
func (NoopManager) GetChannel(string) Channel {
return nil
}
// Start implements common.Runnable. // Start implements common.Runnable.
func (NoopManager) Start() error { return nil } func (NoopManager) Start() error { return nil }

@ -1,7 +1,9 @@
module v2ray.com/core module v2ray.com/core
go 1.15
require ( require (
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
github.com/golang/mock v1.4.4 github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.2 github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.5.2 github.com/google/go-cmp v0.5.2
@ -11,14 +13,12 @@ require (
github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841 github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf
go.starlark.net v0.0.0-20190919145610-979af19b165c go.starlark.net v0.0.0-20200901195727-6e684ef5eeee
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
google.golang.org/grpc v1.31.1 google.golang.org/grpc v1.31.1
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
h12.io/socks v1.0.1 h12.io/socks v1.0.1
) )
go 1.13

@ -1,12 +1,15 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU= github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM= github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -55,12 +58,13 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf h1:d4keT3SwLbrgnEe2zbtijPLgKE15n0ZbvJZzRH/a9GM= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf h1:d4keT3SwLbrgnEe2zbtijPLgKE15n0ZbvJZzRH/a9GM=
github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf/go.mod h1:jTwBnzBuqZP3VX/Z65ErYb9zd4anQprSC7N38TmAp1E= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf/go.mod h1:jTwBnzBuqZP3VX/Z65ErYb9zd4anQprSC7N38TmAp1E=
go.starlark.net v0.0.0-20190919145610-979af19b165c h1:WR7X1xgXJlXhQBdorVc9Db3RhwG+J/kp6bLuMyJjfVw= go.starlark.net v0.0.0-20200901195727-6e684ef5eeee h1:N4eRtIIYHZE5Mw/Km/orb+naLdwAe+lv2HCxRR5rEBw=
go.starlark.net v0.0.0-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.starlark.net v0.0.0-20200901195727-6e684ef5eeee/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
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.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -73,20 +77,22 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -20,9 +20,12 @@ func init() {
wd, err := os.Getwd() wd, err := os.Getwd()
common.Must(err) common.Must(err)
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat"))) if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
}
geositeFilePath := platform.GetAssetLocation("geosite.dat") geositeFilePath := filepath.Join(wd, "geosite.dat")
os.Setenv("v2ray.location.asset", wd)
geositeFile, err := os.OpenFile(geositeFilePath, os.O_CREATE|os.O_WRONLY, 0600) geositeFile, err := os.OpenFile(geositeFilePath, os.O_CREATE|os.O_WRONLY, 0600)
common.Must(err) common.Must(err)
defer geositeFile.Close() defer geositeFile.Close()
@ -46,6 +49,7 @@ func TestDnsConfigParsing(t *testing.T) {
geositePath := platform.GetAssetLocation("geosite.dat") geositePath := platform.GetAssetLocation("geosite.dat")
defer func() { defer func() {
os.Remove(geositePath) os.Remove(geositePath)
os.Unsetenv("v2ray.location.asset")
}() }()
parserCreator := func() func(string) (proto.Message, error) { parserCreator := func() func(string) (proto.Message, error) {

@ -219,14 +219,18 @@ func (c *QUICConfig) Build() (proto.Message, error) {
} }
type DomainSocketConfig struct { type DomainSocketConfig struct {
Path string `json:"path"` Path string `json:"path"`
Abstract bool `json:"abstract"` Abstract bool `json:"abstract"`
Padding bool `json:"padding"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
} }
func (c *DomainSocketConfig) Build() (proto.Message, error) { func (c *DomainSocketConfig) Build() (proto.Message, error) {
return &domainsocket.Config{ return &domainsocket.Config{
Path: c.Path, Path: c.Path,
Abstract: c.Abstract, Abstract: c.Abstract,
Padding: c.Padding,
AcceptProxyProtocol: c.AcceptProxyProtocol,
}, nil }, nil
} }

@ -97,10 +97,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
fb.Type = "serve" fb.Type = "serve"
} else { } else {
switch fb.Dest[0] { switch fb.Dest[0] {
case '@': case '@', '/':
fb.Dest = "\x00" + fb.Dest[1:]
fallthrough
case '/':
fb.Type = "unix" fb.Type = "unix"
default: default:
if _, err := strconv.Atoi(fb.Dest); err == nil { if _, err := strconv.Atoi(fb.Dest); err == nil {

@ -113,7 +113,7 @@ func TestVLessInbound(t *testing.T) {
Alpn: "h2", Alpn: "h2",
Path: "", Path: "",
Type: "unix", Type: "unix",
Dest: "\x00/dev/shm/domain.socket", Dest: "@/dev/shm/domain.socket",
Xver: 2, Xver: 2,
}, },
{ {

@ -260,11 +260,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
} }
return nil return nil
}); err != nil { }); err != nil {
dest := fb.Dest return newError("failed to dial to " + fb.Dest).Base(err).AtWarning()
if dest[0] == '\x00' {
dest = "@" + dest[1:]
}
return newError("failed to dial to " + dest).Base(err).AtWarning()
} }
defer conn.Close() // nolint: errcheck defer conn.Close() // nolint: errcheck

@ -10,6 +10,7 @@ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true NoNewPrivileges=true
ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/config.json ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/config.json
Restart=on-failure Restart=on-failure
RestartPreventExitStatus=23
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

@ -10,6 +10,7 @@ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true NoNewPrivileges=true
ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/%i.json ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/%i.json
Restart=on-failure Restart=on-failure
RestartPreventExitStatus=23
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

@ -9,14 +9,23 @@ import (
) )
const protocolName = "domainsocket" const protocolName = "domainsocket"
const sizeofSunPath = 108
func (c *Config) GetUnixAddr() (*net.UnixAddr, error) { func (c *Config) GetUnixAddr() (*net.UnixAddr, error) {
path := c.Path path := c.Path
if path == "" { if path == "" {
return nil, newError("empty domain socket path") return nil, newError("empty domain socket path")
} }
if c.Abstract && path[0] != '\x00' { if c.Abstract && path[0] != '@' {
path = "\x00" + path path = "@" + path
}
if c.Abstract && c.Padding {
raw := []byte(path)
addr := make([]byte, sizeofSunPath)
for i, c := range raw {
addr[i] = c
}
path = string(addr)
} }
return &net.UnixAddr{ return &net.UnixAddr{
Name: path, Name: path,

@ -30,11 +30,17 @@ type Config struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Path of the domain socket. This overrides the IP/Port parameter from upstream caller. // Path of the domain socket. This overrides the IP/Port parameter from
// upstream caller.
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
// Abstract speicifies whether to use abstract namespace or not. // Abstract speicifies whether to use abstract namespace or not.
// Traditionally Unix domain socket is file system based. Abstract domain socket can be used without acquiring file lock. // Traditionally Unix domain socket is file system based. Abstract domain
// socket can be used without acquiring file lock.
Abstract bool `protobuf:"varint,2,opt,name=abstract,proto3" json:"abstract,omitempty"` Abstract bool `protobuf:"varint,2,opt,name=abstract,proto3" json:"abstract,omitempty"`
// Some apps, eg. haproxy, use the full length of sockaddr_un.sun_path to
// connect(2) or bind(2) when using abstract UDS.
Padding bool `protobuf:"varint,3,opt,name=padding,proto3" json:"padding,omitempty"`
AcceptProxyProtocol bool `protobuf:"varint,4,opt,name=acceptProxyProtocol,proto3" json:"acceptProxyProtocol,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@ -83,6 +89,20 @@ func (x *Config) GetAbstract() bool {
return false return false
} }
func (x *Config) GetPadding() bool {
if x != nil {
return x.Padding
}
return false
}
func (x *Config) GetAcceptProxyProtocol() bool {
if x != nil {
return x.AcceptProxyProtocol
}
return false
}
var File_transport_internet_domainsocket_config_proto protoreflect.FileDescriptor var File_transport_internet_domainsocket_config_proto protoreflect.FileDescriptor
var file_transport_internet_domainsocket_config_proto_rawDesc = []byte{ var file_transport_internet_domainsocket_config_proto_rawDesc = []byte{
@ -91,20 +111,25 @@ var file_transport_internet_domainsocket_config_proto_rawDesc = []byte{
0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x38, 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x06, 0x43,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73, 0x74, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73,
0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73, 0x74, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73,
0x72, 0x61, 0x63, 0x74, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12,
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x30, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72,
0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x6c, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69,
0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f,
0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43,
0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63,
0x6b, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

@ -7,9 +7,15 @@ option java_package = "com.v2ray.core.transport.internet.domainsocket";
option java_multiple_files = true; option java_multiple_files = true;
message Config { message Config {
// Path of the domain socket. This overrides the IP/Port parameter from upstream caller. // Path of the domain socket. This overrides the IP/Port parameter from
string path = 1; // upstream caller.
// Abstract speicifies whether to use abstract namespace or not. string path = 1;
// Traditionally Unix domain socket is file system based. Abstract domain socket can be used without acquiring file lock. // Abstract speicifies whether to use abstract namespace or not.
bool abstract = 2; // Traditionally Unix domain socket is file system based. Abstract domain
// socket can be used without acquiring file lock.
bool abstract = 2;
// Some apps, eg. haproxy, use the full length of sockaddr_un.sun_path to
// connect(2) or bind(2) when using abstract UDS.
bool padding = 3;
bool acceptProxyProtocol = 4;
} }

@ -10,10 +10,12 @@ import (
"os" "os"
"strings" "strings"
"github.com/pires/go-proxyproto"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/session"
"v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet"
"v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/tls"
) )
@ -39,11 +41,23 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, newError("failed to listen domain socket").Base(err).AtWarning() return nil, newError("failed to listen domain socket").Base(err).AtWarning()
} }
ln := &Listener{ var ln *Listener
addr: addr, if settings.AcceptProxyProtocol {
ln: unixListener, policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil }
config: settings, ln = &Listener{
addConn: handler, addr: addr,
ln: &proxyproto.Listener{Listener: unixListener, Policy: policyFunc},
config: settings,
addConn: handler,
}
newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx))
} else {
ln = &Listener{
addr: addr,
ln: unixListener,
config: settings,
addConn: handler,
}
} }
if !settings.Abstract { if !settings.Abstract {

Loading…
Cancel
Save