mirror of https://github.com/v2ray/v2ray-core
commit
b613a9bcb6
|
|
@ -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,9 +17,13 @@ func init() {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
|
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")))
|
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")))
|
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) {
|
||||||
container := &router.GeoIPMatcherContainer{}
|
container := &router.GeoIPMatcherContainer{}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
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")))
|
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")))
|
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withOutbound(outbound *session.Outbound) *Context {
|
|
||||||
return &Context{Outbound: outbound}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func withInbound(inbound *session.Inbound) *Context {
|
func withBackground() routing.Context {
|
||||||
return &Context{Inbound: inbound}
|
return &routing_session.Context{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOutbound(outbound *session.Outbound) routing.Context {
|
||||||
|
return &routing_session.Context{Outbound: outbound}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInbound(inbound *session.Inbound) routing.Context {
|
||||||
|
return &routing_session.Context{Inbound: inbound}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withContent(content *session.Content) routing.Context {
|
||||||
|
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
|
}
|
||||||
|
|
||||||
|
type resolvableContext struct {
|
||||||
|
routing.Context
|
||||||
dnsClient dns.Client
|
dnsClient dns.Client
|
||||||
|
resolvedIPs []net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetTargetIPs() []net.IP {
|
func (ctx *resolvableContext) GetTargetIPs() []net.IP {
|
||||||
if c.Outbound == nil || !c.Outbound.Target.IsValid() {
|
if ips := ctx.Context.GetTargetIPs(); len(ips) != 0 {
|
||||||
return nil
|
return ips
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Outbound.Target.Address.Family().IsIP() {
|
if len(ctx.resolvedIPs) > 0 {
|
||||||
return []net.IP{c.Outbound.Target.Address.IP()}
|
return ctx.resolvedIPs
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Outbound.ResolvedIPs) > 0 {
|
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
||||||
return c.Outbound.ResolvedIPs
|
ips, err := ctx.dnsClient.LookupIP(domain)
|
||||||
}
|
|
||||||
|
|
||||||
if c.dnsClient != nil {
|
|
||||||
domain := c.Outbound.Target.Address.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 }
|
||||||
|
|
||||||
|
|
|
||||||
16
go.mod
16
go.mod
|
|
@ -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
|
|
||||||
|
|
|
||||||
26
go.sum
26
go.sum
|
|
@ -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)
|
||||||
|
|
||||||
|
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")))
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -221,12 +221,16 @@ 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
|
||||||
|
// upstream caller.
|
||||||
string path = 1;
|
string path = 1;
|
||||||
// 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.
|
||||||
bool abstract = 2;
|
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,12 +41,24 @@ 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
|
||||||
|
if settings.AcceptProxyProtocol {
|
||||||
|
policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil }
|
||||||
|
ln = &Listener{
|
||||||
|
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,
|
addr: addr,
|
||||||
ln: unixListener,
|
ln: unixListener,
|
||||||
config: settings,
|
config: settings,
|
||||||
addConn: handler,
|
addConn: handler,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.Abstract {
|
if !settings.Abstract {
|
||||||
ln.locker = &fileLocker{
|
ln.locker = &fileLocker{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue