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') }}
|
||||
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
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
|
@ -46,7 +36,7 @@ jobs:
|
|||
|
||||
- name: Lint other files
|
||||
if: ${{ always() }}
|
||||
uses: github/super-linter@v3.9.2
|
||||
uses: github/super-linter@v3.9.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
|
@ -57,3 +47,13 @@ jobs:
|
|||
VALIDATE_JSON: false
|
||||
VALIDATE_MD: 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/policy"
|
||||
"v2ray.com/core/features/routing"
|
||||
routing_session "v2ray.com/core/features/routing/session"
|
||||
"v2ray.com/core/features/stats"
|
||||
"v2ray.com/core/transport"
|
||||
"v2ray.com/core/transport/pipe"
|
||||
|
@ -265,7 +266,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||
}
|
||||
|
||||
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 {
|
||||
newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
handler = h
|
||||
|
|
|
@ -10,10 +10,11 @@ import (
|
|||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/strmatcher"
|
||||
"v2ray.com/core/features/routing"
|
||||
)
|
||||
|
||||
type Condition interface {
|
||||
Apply(ctx *Context) bool
|
||||
Apply(ctx routing.Context) bool
|
||||
}
|
||||
|
||||
type ConditionChan []Condition
|
||||
|
@ -28,7 +29,8 @@ func (v *ConditionChan) Add(cond Condition) *ConditionChan {
|
|||
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 {
|
||||
if !cond.Apply(ctx) {
|
||||
return false
|
||||
|
@ -85,36 +87,18 @@ func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
|||
return len(m.matchers.Match(domain)) > 0
|
||||
}
|
||||
|
||||
func (m *DomainMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
|
||||
// Apply implements Condition.
|
||||
func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
||||
domain := ctx.GetTargetDomain()
|
||||
if len(domain) == 0 {
|
||||
return false
|
||||
}
|
||||
dest := ctx.Outbound.Target
|
||||
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()
|
||||
return m.ApplyDomain(domain)
|
||||
}
|
||||
|
||||
type MultiGeoIPMatcher struct {
|
||||
matchers []*GeoIPMatcher
|
||||
ipFunc func(*Context) []net.IP
|
||||
onSource bool
|
||||
}
|
||||
|
||||
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
||||
|
@ -129,20 +113,20 @@ func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, e
|
|||
|
||||
matcher := &MultiGeoIPMatcher{
|
||||
matchers: matchers,
|
||||
}
|
||||
|
||||
if onSource {
|
||||
matcher.ipFunc = getIPsFromSource
|
||||
} else {
|
||||
matcher.ipFunc = getIPsFromTarget
|
||||
onSource: onSource,
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func (m *MultiGeoIPMatcher) Apply(ctx *Context) bool {
|
||||
ips := m.ipFunc(ctx)
|
||||
|
||||
// Apply implements Condition.
|
||||
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 _, matcher := range m.matchers {
|
||||
if matcher.Match(ip) {
|
||||
|
@ -166,20 +150,13 @@ func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *PortMatcher) Apply(ctx *Context) bool {
|
||||
var port net.Port
|
||||
// Apply implements Condition.
|
||||
func (v *PortMatcher) Apply(ctx routing.Context) bool {
|
||||
if v.onSource {
|
||||
if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {
|
||||
return false
|
||||
}
|
||||
port = ctx.Inbound.Source.Port
|
||||
return v.port.Contains(ctx.GetSourcePort())
|
||||
} else {
|
||||
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
|
||||
return false
|
||||
}
|
||||
port = ctx.Outbound.Target.Port
|
||||
return v.port.Contains(ctx.GetTargetPort())
|
||||
}
|
||||
return v.port.Contains(port)
|
||||
}
|
||||
|
||||
type NetworkMatcher struct {
|
||||
|
@ -194,11 +171,9 @@ func NewNetworkMatcher(network []net.Network) NetworkMatcher {
|
|||
return matcher
|
||||
}
|
||||
|
||||
func (v NetworkMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {
|
||||
return false
|
||||
}
|
||||
return v.list[int(ctx.Outbound.Target.Network)]
|
||||
// Apply implements Condition.
|
||||
func (v NetworkMatcher) Apply(ctx routing.Context) bool {
|
||||
return v.list[int(ctx.GetNetwork())]
|
||||
}
|
||||
|
||||
type UserMatcher struct {
|
||||
|
@ -217,17 +192,14 @@ func NewUserMatcher(users []string) *UserMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *UserMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Inbound == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user := ctx.Inbound.User
|
||||
if user == nil {
|
||||
// Apply implements Condition.
|
||||
func (v *UserMatcher) Apply(ctx routing.Context) bool {
|
||||
user := ctx.GetUser()
|
||||
if len(user) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, u := range v.user {
|
||||
if u == user.Email {
|
||||
if u == user {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -250,11 +222,12 @@ func NewInboundTagMatcher(tags []string) *InboundTagMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *InboundTagMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Inbound == nil || len(ctx.Inbound.Tag) == 0 {
|
||||
// Apply implements Condition.
|
||||
func (v *InboundTagMatcher) Apply(ctx routing.Context) bool {
|
||||
tag := ctx.GetInboundTag()
|
||||
if len(tag) == 0 {
|
||||
return false
|
||||
}
|
||||
tag := ctx.Inbound.Tag
|
||||
for _, t := range v.tags {
|
||||
if t == tag {
|
||||
return true
|
||||
|
@ -281,18 +254,17 @@ func NewProtocolMatcher(protocols []string) *ProtocolMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *ProtocolMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Content == nil {
|
||||
// Apply implements Condition.
|
||||
func (m *ProtocolMatcher) Apply(ctx routing.Context) bool {
|
||||
protocol := ctx.GetProtocol()
|
||||
if len(protocol) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
protocol := ctx.Content.Protocol
|
||||
for _, p := range m.protocols {
|
||||
if strings.HasPrefix(protocol, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -343,9 +315,11 @@ func (m *AttributeMatcher) Match(attrs map[string]interface{}) bool {
|
|||
return satisfied != nil && bool(satisfied.Truth())
|
||||
}
|
||||
|
||||
func (m *AttributeMatcher) Apply(ctx *Context) bool {
|
||||
if ctx.Content == nil {
|
||||
// Apply implements Condition.
|
||||
func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
|
||||
attributes := ctx.GetAttributes()
|
||||
if attributes == nil {
|
||||
return false
|
||||
}
|
||||
return m.Match(ctx.Content.Attributes)
|
||||
return m.Match(attributes)
|
||||
}
|
||||
|
|
|
@ -17,8 +17,12 @@ func init() {
|
|||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.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")))
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -17,27 +17,41 @@ import (
|
|||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/protocol/http"
|
||||
"v2ray.com/core/common/session"
|
||||
"v2ray.com/core/features/routing"
|
||||
routing_session "v2ray.com/core/features/routing/session"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.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")))
|
||||
}
|
||||
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 withOutbound(outbound *session.Outbound) *Context {
|
||||
return &Context{Outbound: outbound}
|
||||
func withBackground() routing.Context {
|
||||
return &routing_session.Context{}
|
||||
}
|
||||
|
||||
func withInbound(inbound *session.Inbound) *Context {
|
||||
return &Context{Inbound: inbound}
|
||||
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) {
|
||||
type ruleTest struct {
|
||||
input *Context
|
||||
input routing.Context
|
||||
output bool
|
||||
}
|
||||
|
||||
|
@ -88,7 +102,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
output: false,
|
||||
},
|
||||
{
|
||||
input: &Context{},
|
||||
input: withBackground(),
|
||||
output: false,
|
||||
},
|
||||
},
|
||||
|
@ -124,7 +138,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
output: true,
|
||||
},
|
||||
{
|
||||
input: &Context{},
|
||||
input: withBackground(),
|
||||
output: false,
|
||||
},
|
||||
},
|
||||
|
@ -164,7 +178,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
output: true,
|
||||
},
|
||||
{
|
||||
input: &Context{},
|
||||
input: withBackground(),
|
||||
output: false,
|
||||
},
|
||||
},
|
||||
|
@ -205,7 +219,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
output: false,
|
||||
},
|
||||
{
|
||||
input: &Context{},
|
||||
input: withBackground(),
|
||||
output: false,
|
||||
},
|
||||
},
|
||||
|
@ -216,7 +230,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
},
|
||||
test: []ruleTest{
|
||||
{
|
||||
input: &Context{Content: &session.Content{Protocol: (&http.SniffHeader{}).Protocol()}},
|
||||
input: withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}),
|
||||
output: true,
|
||||
},
|
||||
},
|
||||
|
@ -299,7 +313,7 @@ func TestRoutingRule(t *testing.T) {
|
|||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ package router
|
|||
import (
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/features/outbound"
|
||||
"v2ray.com/core/features/routing"
|
||||
)
|
||||
|
||||
// CIDRList is an alias of []*CIDR to provide sort.Interface.
|
||||
|
@ -59,7 +60,8 @@ func (r *Rule) GetTag() (string, error) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"v2ray.com/core"
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/session"
|
||||
"v2ray.com/core/features/dns"
|
||||
"v2ray.com/core/features/outbound"
|
||||
"v2ray.com/core/features/routing"
|
||||
|
@ -74,7 +73,8 @@ func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error
|
|||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -82,37 +82,26 @@ func (r *Router) PickRoute(ctx context.Context) (string, error) {
|
|||
return rule.GetTag()
|
||||
}
|
||||
|
||||
func isDomainOutbound(outbound *session.Outbound) bool {
|
||||
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),
|
||||
}
|
||||
|
||||
func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, error) {
|
||||
if r.domainStrategy == Config_IpOnDemand {
|
||||
sessionContext.dnsClient = r.dns
|
||||
ctx = ContextWithDNSClient(ctx, r.dns)
|
||||
}
|
||||
|
||||
for _, rule := range r.rules {
|
||||
if rule.Apply(sessionContext) {
|
||||
if rule.Apply(ctx) {
|
||||
return rule, nil
|
||||
}
|
||||
}
|
||||
|
||||
if r.domainStrategy != Config_IpIfNonMatch || !isDomainOutbound(sessionContext.Outbound) {
|
||||
if r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 {
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
|
||||
sessionContext.dnsClient = r.dns
|
||||
ctx = ContextWithDNSClient(ctx, r.dns)
|
||||
|
||||
// Try applying rules again if we have IPs.
|
||||
for _, rule := range r.rules {
|
||||
if rule.Apply(sessionContext) {
|
||||
if rule.Apply(ctx) {
|
||||
return rule, nil
|
||||
}
|
||||
}
|
||||
|
@ -135,32 +124,30 @@ func (*Router) Type() interface{} {
|
|||
return routing.RouterType()
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Inbound *session.Inbound
|
||||
Outbound *session.Outbound
|
||||
Content *session.Content
|
||||
|
||||
dnsClient dns.Client
|
||||
// ContextWithDNSClient creates a new routing context with domain resolving capability. Resolved domain IPs can be retrieved by GetTargetIPs().
|
||||
func ContextWithDNSClient(ctx routing.Context, client dns.Client) routing.Context {
|
||||
return &resolvableContext{Context: ctx, dnsClient: client}
|
||||
}
|
||||
|
||||
func (c *Context) GetTargetIPs() []net.IP {
|
||||
if c.Outbound == nil || !c.Outbound.Target.IsValid() {
|
||||
return nil
|
||||
type resolvableContext struct {
|
||||
routing.Context
|
||||
dnsClient dns.Client
|
||||
resolvedIPs []net.IP
|
||||
}
|
||||
|
||||
func (ctx *resolvableContext) GetTargetIPs() []net.IP {
|
||||
if ips := ctx.Context.GetTargetIPs(); len(ips) != 0 {
|
||||
return ips
|
||||
}
|
||||
|
||||
if c.Outbound.Target.Address.Family().IsIP() {
|
||||
return []net.IP{c.Outbound.Target.Address.IP()}
|
||||
if len(ctx.resolvedIPs) > 0 {
|
||||
return ctx.resolvedIPs
|
||||
}
|
||||
|
||||
if len(c.Outbound.ResolvedIPs) > 0 {
|
||||
return c.Outbound.ResolvedIPs
|
||||
}
|
||||
|
||||
if c.dnsClient != nil {
|
||||
domain := c.Outbound.Target.Address.Domain()
|
||||
ips, err := c.dnsClient.LookupIP(domain)
|
||||
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
||||
ips, err := ctx.dnsClient.LookupIP(domain)
|
||||
if err == nil {
|
||||
c.Outbound.ResolvedIPs = ips
|
||||
ctx.resolvedIPs = ips
|
||||
return ips
|
||||
}
|
||||
newError("resolve ip for ", domain).Base(err).WriteToLog()
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/session"
|
||||
"v2ray.com/core/features/outbound"
|
||||
routing_session "v2ray.com/core/features/routing/session"
|
||||
"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)})
|
||||
tag, err := r.PickRoute(ctx)
|
||||
tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
|
||||
common.Must(err)
|
||||
if tag != "test" {
|
||||
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)})
|
||||
tag, err := r.PickRoute(ctx)
|
||||
tag, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
|
||||
common.Must(err)
|
||||
if tag != "test" {
|
||||
t.Error("expect tag 'test', bug actually ", tag)
|
||||
|
@ -120,7 +121,7 @@ func TestIPOnDemand(t *testing.T) {
|
|||
common.Must(r.Init(config, mockDns, nil))
|
||||
|
||||
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)
|
||||
if tag != "test" {
|
||||
t.Error("expect tag 'test', bug actually ", tag)
|
||||
|
@ -155,7 +156,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
|||
common.Must(r.Init(config, mockDns, nil))
|
||||
|
||||
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)
|
||||
if tag != "test" {
|
||||
t.Error("expect tag 'test', bug actually ", tag)
|
||||
|
@ -189,7 +190,7 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
|||
common.Must(r.Init(config, mockDns, nil))
|
||||
|
||||
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)
|
||||
if tag != "test" {
|
||||
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.")
|
||||
}
|
||||
|
||||
manager.Visit(func(name string, c feature_stats.Counter) bool {
|
||||
manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
if matcher.Match(name) {
|
||||
var value int64
|
||||
if request.Reset_ {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"v2ray.com/core/features/stats"
|
||||
)
|
||||
|
@ -32,15 +33,76 @@ func (c *Counter) Add(delta int64) int64 {
|
|||
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.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
counters map[string]*Counter
|
||||
channels map[string]*Channel
|
||||
}
|
||||
|
||||
func NewManager(ctx context.Context, config *Config) (*Manager, error) {
|
||||
m := &Manager{
|
||||
counters: make(map[string]*Counter),
|
||||
channels: make(map[string]*Channel),
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
@ -50,6 +112,7 @@ func (*Manager) Type() interface{} {
|
|||
return stats.ManagerType()
|
||||
}
|
||||
|
||||
// RegisterCounter implements stats.Manager.
|
||||
func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
@ -63,18 +126,7 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (m *Manager) UnregisterCounter(name string) error {
|
||||
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
|
||||
}
|
||||
|
||||
// GetCounter implements stats.Manager.
|
||||
func (m *Manager) GetCounter(name string) stats.Counter {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
@ -85,7 +137,8 @@ func (m *Manager) GetCounter(name string) stats.Counter {
|
|||
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()
|
||||
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.
|
||||
func (m *Manager) Start() error {
|
||||
return nil
|
||||
|
@ -105,3 +184,4 @@ func (m *Manager) Start() error {
|
|||
func (m *Manager) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,16 @@ package stats_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "v2ray.com/core/app/stats"
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/features/stats"
|
||||
)
|
||||
|
||||
func TestInternface(t *testing.T) {
|
||||
func TestInterface(t *testing.T) {
|
||||
_ = (stats.Manager)(new(Manager))
|
||||
}
|
||||
|
||||
|
@ -33,3 +35,317 @@ func TestStatsCounter(t *testing.T) {
|
|||
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
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.14.7'
|
||||
version: '1.15.1'
|
||||
- script: |
|
||||
mkdir triggersrc
|
||||
ls -I "triggersrc" | xargs cp -rf -t triggersrc
|
||||
|
|
|
@ -51,8 +51,6 @@ type Outbound struct {
|
|||
Target net.Destination
|
||||
// Gateway address
|
||||
Gateway net.Address
|
||||
// ResolvedIPs is the resolved IP addresses, if the Targe is a domain address.
|
||||
ResolvedIPs []net.IP
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/features"
|
||||
)
|
||||
|
||||
// Router is a feature to choose an outbound tag for the given request.
|
||||
//
|
||||
// v2ray:api:stable
|
||||
// v2ray:api:beta
|
||||
type Router interface {
|
||||
features.Feature
|
||||
|
||||
// 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.
|
||||
|
@ -33,7 +31,7 @@ func (DefaultRouter) Type() interface{} {
|
|||
}
|
||||
|
||||
// PickRoute implements Router.
|
||||
func (DefaultRouter) PickRoute(ctx context.Context) (string, error) {
|
||||
func (DefaultRouter) PickRoute(ctx Context) (string, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// v2ray:api:stable
|
||||
|
@ -27,6 +41,11 @@ type Manager interface {
|
|||
UnregisterCounter(string) error
|
||||
// GetCounter returns a counter by its identifier.
|
||||
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.
|
||||
|
@ -39,6 +58,16 @@ func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
|
|||
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.
|
||||
//
|
||||
// v2ray:api:stable
|
||||
|
@ -69,6 +98,16 @@ func (NoopManager) GetCounter(string) Counter {
|
|||
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.
|
||||
func (NoopManager) Start() error { return nil }
|
||||
|
||||
|
|
16
go.mod
16
go.mod
|
@ -1,7 +1,9 @@
|
|||
module v2ray.com/core
|
||||
|
||||
go 1.15
|
||||
|
||||
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/protobuf v1.4.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/stretchr/testify v1.6.1
|
||||
github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf
|
||||
go.starlark.net v0.0.0-20190919145610-979af19b165c
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
||||
go.starlark.net v0.0.0-20200901195727-6e684ef5eeee
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
|
||||
google.golang.org/grpc v1.31.1
|
||||
google.golang.org/protobuf v1.25.0
|
||||
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=
|
||||
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/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/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/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-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/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/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/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf h1:d4keT3SwLbrgnEe2zbtijPLgKE15n0ZbvJZzRH/a9GM=
|
||||
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-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
|
||||
go.starlark.net v0.0.0-20200901195727-6e684ef5eeee h1:N4eRtIIYHZE5Mw/Km/orb+naLdwAe+lv2HCxRR5rEBw=
|
||||
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-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-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/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=
|
||||
|
@ -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-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-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
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/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-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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
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-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-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-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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
@ -20,9 +20,12 @@ func init() {
|
|||
wd, err := os.Getwd()
|
||||
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)
|
||||
common.Must(err)
|
||||
defer geositeFile.Close()
|
||||
|
@ -46,6 +49,7 @@ func TestDnsConfigParsing(t *testing.T) {
|
|||
geositePath := platform.GetAssetLocation("geosite.dat")
|
||||
defer func() {
|
||||
os.Remove(geositePath)
|
||||
os.Unsetenv("v2ray.location.asset")
|
||||
}()
|
||||
|
||||
parserCreator := func() func(string) (proto.Message, error) {
|
||||
|
|
|
@ -219,14 +219,18 @@ func (c *QUICConfig) Build() (proto.Message, error) {
|
|||
}
|
||||
|
||||
type DomainSocketConfig struct {
|
||||
Path string `json:"path"`
|
||||
Abstract bool `json:"abstract"`
|
||||
Path string `json:"path"`
|
||||
Abstract bool `json:"abstract"`
|
||||
Padding bool `json:"padding"`
|
||||
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||
}
|
||||
|
||||
func (c *DomainSocketConfig) Build() (proto.Message, error) {
|
||||
return &domainsocket.Config{
|
||||
Path: c.Path,
|
||||
Abstract: c.Abstract,
|
||||
Path: c.Path,
|
||||
Abstract: c.Abstract,
|
||||
Padding: c.Padding,
|
||||
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -97,10 +97,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
|||
fb.Type = "serve"
|
||||
} else {
|
||||
switch fb.Dest[0] {
|
||||
case '@':
|
||||
fb.Dest = "\x00" + fb.Dest[1:]
|
||||
fallthrough
|
||||
case '/':
|
||||
case '@', '/':
|
||||
fb.Type = "unix"
|
||||
default:
|
||||
if _, err := strconv.Atoi(fb.Dest); err == nil {
|
||||
|
|
|
@ -113,7 +113,7 @@ func TestVLessInbound(t *testing.T) {
|
|||
Alpn: "h2",
|
||||
Path: "",
|
||||
Type: "unix",
|
||||
Dest: "\x00/dev/shm/domain.socket",
|
||||
Dest: "@/dev/shm/domain.socket",
|
||||
Xver: 2,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -260,11 +260,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
|
|||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
dest := fb.Dest
|
||||
if dest[0] == '\x00' {
|
||||
dest = "@" + dest[1:]
|
||||
}
|
||||
return newError("failed to dial to " + dest).Base(err).AtWarning()
|
||||
return newError("failed to dial to " + fb.Dest).Base(err).AtWarning()
|
||||
}
|
||||
defer conn.Close() // nolint: errcheck
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
|||
NoNewPrivileges=true
|
||||
ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/config.json
|
||||
Restart=on-failure
|
||||
RestartPreventExitStatus=23
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -10,6 +10,7 @@ AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
|||
NoNewPrivileges=true
|
||||
ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/%i.json
|
||||
Restart=on-failure
|
||||
RestartPreventExitStatus=23
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -9,14 +9,23 @@ import (
|
|||
)
|
||||
|
||||
const protocolName = "domainsocket"
|
||||
const sizeofSunPath = 108
|
||||
|
||||
func (c *Config) GetUnixAddr() (*net.UnixAddr, error) {
|
||||
path := c.Path
|
||||
if path == "" {
|
||||
return nil, newError("empty domain socket path")
|
||||
}
|
||||
if c.Abstract && path[0] != '\x00' {
|
||||
path = "\x00" + path
|
||||
if c.Abstract && path[0] != '@' {
|
||||
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{
|
||||
Name: path,
|
||||
|
|
|
@ -30,11 +30,17 @@ type Config struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
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"`
|
||||
// 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"`
|
||||
// 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() {
|
||||
|
@ -83,6 +89,20 @@ func (x *Config) GetAbstract() bool {
|
|||
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_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,
|
||||
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,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x38, 0x0a, 0x06, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73, 0x74,
|
||||
0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73, 0x74,
|
||||
0x72, 0x61, 0x63, 0x74, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 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, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x63, 0x6f, 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,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x06, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73,
|
||||
0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73,
|
||||
0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12,
|
||||
0x30, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63,
|
||||
0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||
0x6c, 0x42, 0x8f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 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, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f,
|
||||
0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
|
||||
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 (
|
||||
|
|
|
@ -7,9 +7,15 @@ option java_package = "com.v2ray.core.transport.internet.domainsocket";
|
|||
option java_multiple_files = true;
|
||||
|
||||
message Config {
|
||||
// Path of the domain socket. This overrides the IP/Port parameter from upstream caller.
|
||||
string path = 1;
|
||||
// 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.
|
||||
bool abstract = 2;
|
||||
// Path of the domain socket. This overrides the IP/Port parameter from
|
||||
// upstream caller.
|
||||
string path = 1;
|
||||
// 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.
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/session"
|
||||
"v2ray.com/core/transport/internet"
|
||||
"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()
|
||||
}
|
||||
|
||||
ln := &Listener{
|
||||
addr: addr,
|
||||
ln: unixListener,
|
||||
config: settings,
|
||||
addConn: handler,
|
||||
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,
|
||||
ln: unixListener,
|
||||
config: settings,
|
||||
addConn: handler,
|
||||
}
|
||||
}
|
||||
|
||||
if !settings.Abstract {
|
||||
|
|
Loading…
Reference in New Issue