├── WORKSPACE ├── tools ├── conf │ ├── conf.go │ ├── serial │ │ ├── serial.go │ │ ├── errors.generated.go │ │ ├── loader_test.go │ │ └── loader.go │ ├── buildable.go │ ├── errors.generated.go │ ├── command │ │ ├── errors.generated.go │ │ └── command.go │ ├── dns_proxy.go │ ├── blackhole_test.go │ ├── policy_test.go │ ├── http_test.go │ ├── dokodemo.go │ ├── shadowsocks_test.go │ ├── dokodemo_test.go │ ├── http.go │ ├── mtproto_test.go │ ├── reverse_test.go │ ├── general_test.go │ ├── freedom_test.go │ ├── api.go │ ├── reverse.go │ ├── blackhole.go │ ├── log.go │ ├── freedom.go │ ├── mtproto.go │ ├── socks_test.go │ ├── loader.go │ ├── policy.go │ ├── dns_test.go │ ├── transport.go │ ├── vmess_test.go │ ├── socks.go │ ├── shadowsocks.go │ ├── common.go │ ├── transport_test.go │ ├── vmess.go │ ├── dns.go │ ├── common_test.go │ ├── router_test.go │ ├── transport_authenticators.go │ ├── v2ray_test.go │ ├── router.go │ ├── v2ray.go │ └── transport_internet.go ├── control │ ├── control.go │ ├── errors.generated.go │ ├── main │ │ ├── BUILD │ │ ├── main.go │ │ └── targets.bzl │ ├── uuid.go │ ├── command.go │ ├── fetch.go │ ├── love.go │ ├── cert.go │ ├── api.go │ └── verify.go └── vprotogen │ └── main.go ├── assert └── assert.go ├── bazel ├── BUILD ├── matrix.bzl ├── gpg.bzl ├── build.bzl └── zip.bzl ├── install ├── daemon │ └── daemon.sh └── nupkg │ ├── readme.txt │ └── core.nuspec ├── ext.go ├── .gitignore ├── docker ├── dev │ ├── config.json │ └── Dockerfile └── official │ ├── config.json │ └── Dockerfile ├── plugins └── simpleplug │ └── main │ └── plugin.go ├── .vscode ├── settings.json └── tasks.json ├── README.md ├── azure-pipelines.template.yml ├── sysio └── sysio.go ├── LICENSE ├── azure-pipelines.yml ├── testing └── coverage │ └── coverall ├── demo └── dialer │ └── main.go ├── snap └── snapcraft.yaml └── encoding └── json ├── reader_test.go └── reader.go /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "v2ray_ext") 2 | -------------------------------------------------------------------------------- /tools/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | //go:generate errorgen 4 | -------------------------------------------------------------------------------- /assert/assert.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | func NotUsedAnyMore() {} 4 | -------------------------------------------------------------------------------- /tools/conf/serial/serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | //go:generate errorgen 4 | -------------------------------------------------------------------------------- /tools/control/control.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | //go:generate errorgen 4 | -------------------------------------------------------------------------------- /bazel/BUILD: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "rules", 3 | srcs = glob(["*.bzl"]), 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /tools/conf/buildable.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import "github.com/golang/protobuf/proto" 4 | 5 | type Buildable interface { 6 | Build() (proto.Message, error) 7 | } 8 | -------------------------------------------------------------------------------- /install/daemon/daemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A simple script to run V2Ray in background. 4 | 5 | if test -t 1; then 6 | exec 1>/dev/null 7 | fi 8 | 9 | if test -t 2; then 10 | exec 2>/dev/null 11 | fi 12 | 13 | "$@" & -------------------------------------------------------------------------------- /ext.go: -------------------------------------------------------------------------------- 1 | package ext 2 | 3 | //go:generate go get -u "github.com/golang/protobuf/protoc-gen-go" 4 | //go:generate go get -u "github.com/golang/protobuf/proto" 5 | //go:generate go install "v2ray.com/ext/tools/vprotogen" 6 | //go:generate vprotogen -repo v2ray.com/ext 7 | -------------------------------------------------------------------------------- /tools/conf/errors.generated.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import "v2ray.com/core/common/errors" 4 | 5 | type errPathObjHolder struct{} 6 | 7 | func newError(values ...interface{}) *errors.Error { 8 | return errors.New(values...).WithPathObj(errPathObjHolder{}) 9 | } 10 | -------------------------------------------------------------------------------- /tools/conf/serial/errors.generated.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import "v2ray.com/core/common/errors" 4 | 5 | type errPathObjHolder struct{} 6 | 7 | func newError(values ...interface{}) *errors.Error { 8 | return errors.New(values...).WithPathObj(errPathObjHolder{}) 9 | } 10 | -------------------------------------------------------------------------------- /tools/control/errors.generated.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import "v2ray.com/core/common/errors" 4 | 5 | type errPathObjHolder struct{} 6 | 7 | func newError(values ...interface{}) *errors.Error { 8 | return errors.New(values...).WithPathObj(errPathObjHolder{}) 9 | } 10 | -------------------------------------------------------------------------------- /tools/conf/command/errors.generated.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "v2ray.com/core/common/errors" 4 | 5 | type errPathObjHolder struct{} 6 | 7 | func newError(values ...interface{}) *errors.Error { 8 | return errors.New(values...).WithPathObj(errPathObjHolder{}) 9 | } 10 | -------------------------------------------------------------------------------- /tools/conf/dns_proxy.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "v2ray.com/core/proxy/dns" 6 | ) 7 | 8 | type DnsOutboundConfig struct{} 9 | 10 | func (c *DnsOutboundConfig) Build() (proto.Message, error) { 11 | return new(dns.Config), nil 12 | } 13 | -------------------------------------------------------------------------------- /tools/control/main/BUILD: -------------------------------------------------------------------------------- 1 | load("//bazel:build.bzl", "foreign_go_binary") 2 | load("//bazel:gpg.bzl", "gpg_sign") 3 | load("//bazel:matrix.bzl", "SUPPORTED_MATRIX") 4 | load("//tools/control/main:targets.bzl", "gen_targets") 5 | 6 | package(default_visibility=["//visibility:public"]) 7 | 8 | gen_targets(SUPPORTED_MATRIX) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | *.DS_Store 27 | bazel-* 28 | -------------------------------------------------------------------------------- /bazel/matrix.bzl: -------------------------------------------------------------------------------- 1 | SUPPORTED_MATRIX = [ 2 | ("windows", "amd64"), 3 | ("windows", "386"), 4 | ("darwin", "amd64"), 5 | ("linux", "amd64"), 6 | ("linux", "386"), 7 | ("linux", "arm64"), 8 | ("linux", "arm"), 9 | ("linux", "mips64"), 10 | ("linux", "mips"), 11 | ("linux", "mips64le"), 12 | ("linux", "mipsle"), 13 | ("linux", "ppc64"), 14 | ("linux", "ppc64le"), 15 | ("linux", "s390x"), 16 | ("freebsd", "amd64"), 17 | ("freebsd", "386"), 18 | ("openbsd", "amd64"), 19 | ("openbsd", "386"), 20 | ("dragonfly", "amd64"), 21 | ] 22 | -------------------------------------------------------------------------------- /docker/dev/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log" : { 3 | "access": "/var/log/v2ray/access.log", 4 | "error": "/var/log/v2ray/error.log", 5 | "loglevel": "warning" 6 | }, 7 | "inbounds": [{ 8 | "port": 8000, 9 | "protocol": "vmess", 10 | "settings": { 11 | "clients": [ 12 | { 13 | "id": "11c2a696-0366-4524-b8f0-9a9c21512b02", 14 | "level": 1, 15 | "alterId": 64 16 | } 17 | ] 18 | } 19 | }], 20 | "outbounds": [{ 21 | "protocol": "freedom", 22 | "settings": {} 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /docker/official/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log" : { 3 | "access": "/var/log/v2ray/access.log", 4 | "error": "/var/log/v2ray/error.log", 5 | "loglevel": "warning" 6 | }, 7 | "inbounds": [{ 8 | "port": 8001, 9 | "protocol": "vmess", 10 | "settings": { 11 | "clients": [ 12 | { 13 | "id": "60ca58e9-003e-4c01-98de-c2223ae49153", 14 | "level": 1, 15 | "alterId": 64 16 | } 17 | ] 18 | } 19 | }], 20 | "outbounds": [{ 21 | "protocol": "freedom", 22 | "settings": {} 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /plugins/simpleplug/main/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "v2ray.com/core/common" 7 | ) 8 | 9 | type Config struct { 10 | Name string 11 | } 12 | 13 | type Instance struct { 14 | config *Config 15 | } 16 | 17 | func (t *Instance) Name() string { 18 | return t.config.Name 19 | } 20 | 21 | func init() { 22 | common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 23 | return &Instance{ 24 | config: config.(*Config), 25 | }, nil 26 | }) 27 | } 28 | 29 | func main() { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tools/control/uuid.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "fmt" 5 | 6 | "v2ray.com/core/common" 7 | "v2ray.com/core/common/uuid" 8 | ) 9 | 10 | type UUIDCommand struct{} 11 | 12 | func (c *UUIDCommand) Name() string { 13 | return "uuid" 14 | } 15 | 16 | func (c *UUIDCommand) Description() Description { 17 | return Description{ 18 | Short: "Generate new UUIDs", 19 | Usage: []string{"v2ctl uuid"}, 20 | } 21 | } 22 | 23 | func (c *UUIDCommand) Execute([]string) error { 24 | u := uuid.New() 25 | fmt.Println(u.String()) 26 | return nil 27 | } 28 | 29 | func init() { 30 | common.Must(RegisterCommand(&UUIDCommand{})) 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | 5 | "go.lintTool": "gometalinter", 6 | "go.lintFlags": [ 7 | "--enable-gc", 8 | "--no-config", 9 | "--exclude=.*\\.pb\\.go", 10 | "--disable=gas", 11 | "--disable=gocyclo", 12 | "--disable=gosec", 13 | "--disable=interfacer", 14 | "--deadline=5m" 15 | ], 16 | "go.formatTool": "goimports", 17 | 18 | "protoc": { 19 | "options": [ 20 | "--proto_path=${env.GOPATH}/src/", 21 | "--proto_path=${env.GOPATH}/src/github.com/google/protobuf/src" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # V2Ray Extensions 2 | 3 | [![Build Status][1]][2] [![codecov.io][3]][4] [![codebeat badge][5]][6] 4 | 5 | [1]: https://dev.azure.com/v2ray/core/_apis/build/status/v2ray.ext "Build Status badge" 6 | [2]: https://dev.azure.com/v2ray/core/_build/latest?definitionId=2 "Build Status link" 7 | [3]: https://codecov.io/github/v2ray/ext/coverage.svg?branch=master "Coverage badge" 8 | [4]: https://codecov.io/github/v2ray/ext?branch=master "Codecov Status" 9 | [5]: https://codebeat.co/badges/3a2163a8-cb1a-41ba-a860-bf60e2fa5050 "CodeBeat badge" 10 | [6]: https://codebeat.co/projects/github-com-v2ray-ext-master "CodeBeat status" 11 | 12 | More utilities 13 | 14 | ====== 15 | 16 | Docker moved to https://github.com/v2fly/docker -------------------------------------------------------------------------------- /azure-pipelines.template.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ${{ parameters.name }} 3 | timeoutInMinutes: 30 4 | 5 | pool: 6 | vmImage: ${{ parameters.vmImage }} 7 | 8 | variables: 9 | GOPATH: '$(system.defaultWorkingDirectory)' 10 | 11 | steps: 12 | - checkout: none 13 | - task: GoTool@0 14 | inputs: 15 | version: '1.11.5' 16 | - script: | 17 | go version 18 | go get -v -t -d v2ray.com/core/... 19 | go get -v -t -d v2ray.com/ext/... 20 | workingDirectory: '$(system.defaultWorkingDirectory)' 21 | displayName: 'Fetch sources' 22 | - script: | 23 | go test -p 1 -timeout 30m -v v2ray.com/ext/... 24 | workingDirectory: '$(system.defaultWorkingDirectory)' 25 | displayName: 'Test' 26 | -------------------------------------------------------------------------------- /tools/conf/blackhole_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/serial" 7 | "v2ray.com/core/proxy/blackhole" 8 | . "v2ray.com/ext/tools/conf" 9 | ) 10 | 11 | func TestHTTPResponseJSON(t *testing.T) { 12 | creator := func() Buildable { 13 | return new(BlackholeConfig) 14 | } 15 | 16 | runMultiTestCase(t, []TestCase{ 17 | { 18 | Input: `{ 19 | "response": { 20 | "type": "http" 21 | } 22 | }`, 23 | Parser: loadJSON(creator), 24 | Output: &blackhole.Config{ 25 | Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}), 26 | }, 27 | }, 28 | { 29 | Input: `{}`, 30 | Parser: loadJSON(creator), 31 | Output: &blackhole.Config{}, 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /tools/conf/policy_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common" 7 | . "v2ray.com/ext/tools/conf" 8 | ) 9 | 10 | func TestBufferSize(t *testing.T) { 11 | cases := []struct { 12 | Input int32 13 | Output int32 14 | }{ 15 | { 16 | Input: 0, 17 | Output: 0, 18 | }, 19 | { 20 | Input: -1, 21 | Output: -1, 22 | }, 23 | { 24 | Input: 1, 25 | Output: 1024, 26 | }, 27 | } 28 | 29 | for _, c := range cases { 30 | bs := int32(c.Input) 31 | pConf := Policy{ 32 | BufferSize: &bs, 33 | } 34 | p, err := pConf.Build() 35 | common.Must(err) 36 | if p.Buffer.Connection != c.Output { 37 | t.Error("expected buffer size ", c.Output, " but got ", p.Buffer.Connection) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tools/conf/http_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/proxy/http" 7 | . "v2ray.com/ext/tools/conf" 8 | ) 9 | 10 | func TestHttpServerConfig(t *testing.T) { 11 | creator := func() Buildable { 12 | return new(HttpServerConfig) 13 | } 14 | 15 | runMultiTestCase(t, []TestCase{ 16 | { 17 | Input: `{ 18 | "timeout": 10, 19 | "accounts": [ 20 | { 21 | "user": "my-username", 22 | "pass": "my-password" 23 | } 24 | ], 25 | "allowTransparent": true, 26 | "userLevel": 1 27 | }`, 28 | Parser: loadJSON(creator), 29 | Output: &http.ServerConfig{ 30 | Accounts: map[string]string{ 31 | "my-username": "my-password", 32 | }, 33 | AllowTransparent: true, 34 | UserLevel: 1, 35 | Timeout: 10, 36 | }, 37 | }, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "go", 4 | "type": "shell", 5 | "presentation":{ 6 | "echo": true, 7 | "reveal": "always", 8 | "focus": false, 9 | "panel": "shared" 10 | }, 11 | "tasks": [ 12 | { 13 | "label": "build", 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | }, 18 | "args": ["v2ray.com/ext/..."], 19 | "problemMatcher": { 20 | "owner": "go", 21 | "fileLocation": ["relative", "${workspaceRoot}"], 22 | "pattern": { 23 | "regexp": "^([^:]+\\.go):(\\d+):(.*)", 24 | "file": 1, 25 | "line": 2, 26 | "message": 3 27 | } 28 | } 29 | }, 30 | { 31 | "label": "test", 32 | "args": ["-p", "1", "v2ray.com/ext/..."], 33 | "group": "test" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /tools/conf/dokodemo.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "v2ray.com/core/proxy/dokodemo" 6 | ) 7 | 8 | type DokodemoConfig struct { 9 | Host *Address `json:"address"` 10 | PortValue uint16 `json:"port"` 11 | NetworkList *NetworkList `json:"network"` 12 | TimeoutValue uint32 `json:"timeout"` 13 | Redirect bool `json:"followRedirect"` 14 | UserLevel uint32 `json:"userLevel"` 15 | } 16 | 17 | func (v *DokodemoConfig) Build() (proto.Message, error) { 18 | config := new(dokodemo.Config) 19 | if v.Host != nil { 20 | config.Address = v.Host.Build() 21 | } 22 | config.Port = uint32(v.PortValue) 23 | config.Networks = v.NetworkList.Build() 24 | config.Timeout = v.TimeoutValue 25 | config.FollowRedirect = v.Redirect 26 | config.UserLevel = v.UserLevel 27 | return config, nil 28 | } 29 | -------------------------------------------------------------------------------- /install/nupkg/readme.txt: -------------------------------------------------------------------------------- 1 | # V2Ray 内核 2 | 3 | V2Ray 内核可以单独使用,也可以配置其它程序一起使用。 4 | 5 | 官网:https://www.v2ray.com/ 6 | 7 | ## 使用方式 8 | 9 | ### Windows 或 macOS 10 | 11 | 压缩包内的 config.json 是默认的配置文件,无需修改即可使用。配置文件的详细信息可以在官网找到。 12 | 13 | * Windows 中的可执行文件为 v2ray.exe 和 wv2ray.exe。双击即可运行。 14 | * v2ray.exe 是一个命令行程序,启动后可以看到命令行界面。 15 | * wv2ray.exe 是一个后台程序,没有界面,会在后台自动运行。 16 | * macOS 中的可执行文件为 v2ray。右键单击,然后选择使用 Terminal 打开即可。 17 | 18 | ### Linux 19 | 20 | 压缩包中包含多个配置文件,按需使用。 21 | 22 | 可执行程序为 v2ray,启动命令: 23 | 24 | ```bash 25 | v2ray --config= 26 | ``` 27 | 28 | ## 验证文件 29 | 30 | 压缩包中的 .sig 文件为 GPG 签名文件,用来验证对应程序文件的真实性。签名公钥可以在下面的链接找到: 31 | 32 | https://github.com/v2ray/v2ray-core/blob/master/release/verify/official_release.asc 33 | 34 | ## 问题反馈 35 | 36 | * Github: https://github.com/v2ray/v2ray-core 37 | * Telegram: https://t.me/projectv2ray 38 | -------------------------------------------------------------------------------- /sysio/sysio.go: -------------------------------------------------------------------------------- 1 | package sysio 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | 8 | "v2ray.com/core/common/buf" 9 | "v2ray.com/core/common/platform" 10 | ) 11 | 12 | type FileReaderFunc func(path string) (io.ReadCloser, error) 13 | 14 | var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) { 15 | return os.Open(path) 16 | } 17 | 18 | func ReadFile(path string) ([]byte, error) { 19 | reader, err := NewFileReader(path) 20 | if err != nil { 21 | return nil, err 22 | } 23 | defer reader.Close() 24 | 25 | return buf.ReadAllToBytes(reader) 26 | } 27 | 28 | func ReadAsset(file string) ([]byte, error) { 29 | return ReadFile(platform.GetAssetLocation(file)) 30 | } 31 | 32 | func CopyFile(dst string, src string) error { 33 | bytes, err := ReadFile(src) 34 | if err != nil { 35 | return err 36 | } 37 | return ioutil.WriteFile(dst, bytes, 0644) 38 | } 39 | -------------------------------------------------------------------------------- /docker/official/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest as builder 2 | 3 | RUN apt-get update 4 | RUN apt-get install curl -y 5 | RUN curl -L -o /tmp/go.sh https://install.direct/go.sh 6 | RUN chmod +x /tmp/go.sh 7 | RUN /tmp/go.sh 8 | 9 | FROM alpine:latest 10 | 11 | LABEL maintainer "Darian Raymond " 12 | 13 | COPY --from=builder /usr/bin/v2ray/v2ray /usr/bin/v2ray/ 14 | COPY --from=builder /usr/bin/v2ray/v2ctl /usr/bin/v2ray/ 15 | COPY --from=builder /usr/bin/v2ray/geoip.dat /usr/bin/v2ray/ 16 | COPY --from=builder /usr/bin/v2ray/geosite.dat /usr/bin/v2ray/ 17 | COPY config.json /etc/v2ray/config.json 18 | 19 | RUN set -ex && \ 20 | apk --no-cache add ca-certificates && \ 21 | mkdir /var/log/v2ray/ &&\ 22 | chmod +x /usr/bin/v2ray/v2ctl && \ 23 | chmod +x /usr/bin/v2ray/v2ray 24 | 25 | ENV PATH /usr/bin/v2ray:$PATH 26 | 27 | CMD ["v2ray", "-config=/etc/v2ray/config.json"] 28 | -------------------------------------------------------------------------------- /tools/conf/shadowsocks_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/net" 7 | "v2ray.com/core/common/protocol" 8 | "v2ray.com/core/common/serial" 9 | "v2ray.com/core/proxy/shadowsocks" 10 | . "v2ray.com/ext/tools/conf" 11 | ) 12 | 13 | func TestShadowsocksServerConfigParsing(t *testing.T) { 14 | creator := func() Buildable { 15 | return new(ShadowsocksServerConfig) 16 | } 17 | 18 | runMultiTestCase(t, []TestCase{ 19 | { 20 | Input: `{ 21 | "method": "aes-128-cfb", 22 | "password": "v2ray-password" 23 | }`, 24 | Parser: loadJSON(creator), 25 | Output: &shadowsocks.ServerConfig{ 26 | User: &protocol.User{ 27 | Account: serial.ToTypedMessage(&shadowsocks.Account{ 28 | CipherType: shadowsocks.CipherType_AES_128_CFB, 29 | Password: "v2ray-password", 30 | }), 31 | }, 32 | Network: []net.Network{net.Network_TCP}, 33 | }, 34 | }, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /bazel/gpg.bzl: -------------------------------------------------------------------------------- 1 | def _gpg_sign_impl(ctx): 2 | output_file = ctx.actions.declare_file(ctx.file.base.basename + ctx.attr.suffix, sibling = ctx.file.base) 3 | if not ctx.configuration.default_shell_env.get("GPG_PASS"): 4 | ctx.actions.write(output_file, "") 5 | else: 6 | command = "echo ${GPG_PASS} | gpg --pinentry-mode loopback --digest-algo SHA512 --passphrase-fd 0 --output %s --detach-sig %s" % (output_file.path, ctx.file.base.path) 7 | ctx.actions.run_shell( 8 | command = command, 9 | use_default_shell_env = True, 10 | inputs = [ctx.file.base], 11 | outputs = [output_file], 12 | progress_message = "Signing binary", 13 | mnemonic = "gpg", 14 | ) 15 | return [DefaultInfo(files = depset([output_file]))] 16 | 17 | gpg_sign = rule( 18 | implementation = _gpg_sign_impl, 19 | attrs = { 20 | "base": attr.label(allow_single_file=True), 21 | "suffix": attr.string(default=".sig"), 22 | }, 23 | ) 24 | -------------------------------------------------------------------------------- /tools/conf/dokodemo_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/net" 7 | "v2ray.com/core/proxy/dokodemo" 8 | . "v2ray.com/ext/tools/conf" 9 | ) 10 | 11 | func TestDokodemoConfig(t *testing.T) { 12 | creator := func() Buildable { 13 | return new(DokodemoConfig) 14 | } 15 | 16 | runMultiTestCase(t, []TestCase{ 17 | { 18 | Input: `{ 19 | "address": "8.8.8.8", 20 | "port": 53, 21 | "network": "tcp", 22 | "timeout": 10, 23 | "followRedirect": true, 24 | "userLevel": 1 25 | }`, 26 | Parser: loadJSON(creator), 27 | Output: &dokodemo.Config{ 28 | Address: &net.IPOrDomain{ 29 | Address: &net.IPOrDomain_Ip{ 30 | Ip: []byte{8, 8, 8, 8}, 31 | }, 32 | }, 33 | Port: 53, 34 | Networks: []net.Network{net.Network_TCP}, 35 | Timeout: 10, 36 | FollowRedirect: true, 37 | UserLevel: 1, 38 | }, 39 | }, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /tools/conf/http.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "v2ray.com/core/proxy/http" 6 | ) 7 | 8 | type HttpAccount struct { 9 | Username string `json:"user"` 10 | Password string `json:"pass"` 11 | } 12 | 13 | type HttpServerConfig struct { 14 | Timeout uint32 `json:"timeout"` 15 | Accounts []*HttpAccount `json:"accounts"` 16 | Transparent bool `json:"allowTransparent"` 17 | UserLevel uint32 `json:"userLevel"` 18 | } 19 | 20 | func (c *HttpServerConfig) Build() (proto.Message, error) { 21 | config := &http.ServerConfig{ 22 | Timeout: c.Timeout, 23 | AllowTransparent: c.Transparent, 24 | UserLevel: c.UserLevel, 25 | } 26 | 27 | if len(c.Accounts) > 0 { 28 | config.Accounts = make(map[string]string) 29 | for _, account := range c.Accounts { 30 | config.Accounts[account.Username] = account.Password 31 | } 32 | } 33 | 34 | return config, nil 35 | } 36 | -------------------------------------------------------------------------------- /install/nupkg/core.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | V2Ray.Core 5 | V2Ray Core 6 | 3.46 7 | v2ray 8 | v2ray 9 | https://opensource.org/licenses/MIT 10 | http://github.com/v2ray/v2ray-core 11 | https://www.v2ray.com/resources/v2ray_1024.png 12 | false 13 | https://www.v2ray.com/en/welcome/versions.html 14 | Platform for building your own privacy network over internet. 15 | Copyright ©2018 Project V 16 | web utility 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docker/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest as builder 2 | 3 | MAINTAINER admin@v2ray.com 4 | 5 | RUN go get -u v2ray.com/core/... 6 | RUN mkdir -p /usr/bin/v2ray/ 7 | RUN CGO_ENABLED=0 go build -o /usr/bin/v2ray/v2ray v2ray.com/core/main 8 | RUN CGO_ENABLED=0 go build -o /usr/bin/v2ray/v2ctl v2ray.com/core/infra/control/main 9 | RUN cp -r ${GOPATH}/src/v2ray.com/core/release/config/* /usr/bin/v2ray/ 10 | 11 | FROM alpine 12 | 13 | RUN apk update 14 | RUN apk upgrade 15 | RUN apk add ca-certificates && update-ca-certificates 16 | # Change TimeZone 17 | RUN apk add --update tzdata 18 | ENV TZ=Asia/Shanghai 19 | # Clean APK cache 20 | RUN rm -rf /var/cache/apk/* 21 | 22 | RUN mkdir /usr/bin/v2ray/ 23 | RUN mkdir /etc/v2ray/ 24 | RUN mkdir /var/log/v2ray/ 25 | 26 | COPY --from=builder /usr/bin/v2ray /usr/bin/v2ray 27 | 28 | ENV PATH /usr/bin/v2ray/:$PATH 29 | 30 | EXPOSE 8000 31 | COPY config.json /etc/v2ray/config.json 32 | 33 | CMD ["/usr/bin/v2ray/v2ray", "-config=/etc/v2ray/config.json"] 34 | -------------------------------------------------------------------------------- /tools/conf/mtproto_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/protocol" 7 | "v2ray.com/core/common/serial" 8 | "v2ray.com/core/proxy/mtproto" 9 | . "v2ray.com/ext/tools/conf" 10 | ) 11 | 12 | func TestMTProtoServerConfig(t *testing.T) { 13 | creator := func() Buildable { 14 | return new(MTProtoServerConfig) 15 | } 16 | 17 | runMultiTestCase(t, []TestCase{ 18 | { 19 | Input: `{ 20 | "users": [{ 21 | "email": "love@v2ray.com", 22 | "level": 1, 23 | "secret": "b0cbcef5a486d9636472ac27f8e11a9d" 24 | }] 25 | }`, 26 | Parser: loadJSON(creator), 27 | Output: &mtproto.ServerConfig{ 28 | User: []*protocol.User{ 29 | { 30 | Email: "love@v2ray.com", 31 | Level: 1, 32 | Account: serial.ToTypedMessage(&mtproto.Account{ 33 | Secret: []byte{176, 203, 206, 245, 164, 134, 217, 99, 100, 114, 172, 39, 248, 225, 26, 157}, 34 | }), 35 | }, 36 | }, 37 | }, 38 | }, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /tools/conf/reverse_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/app/reverse" 7 | "v2ray.com/ext/tools/conf" 8 | ) 9 | 10 | func TestReverseConfig(t *testing.T) { 11 | creator := func() conf.Buildable { 12 | return new(conf.ReverseConfig) 13 | } 14 | 15 | runMultiTestCase(t, []TestCase{ 16 | { 17 | Input: `{ 18 | "bridges": [{ 19 | "tag": "test", 20 | "domain": "test.v2ray.com" 21 | }] 22 | }`, 23 | Parser: loadJSON(creator), 24 | Output: &reverse.Config{ 25 | BridgeConfig: []*reverse.BridgeConfig{ 26 | {Tag: "test", Domain: "test.v2ray.com"}, 27 | }, 28 | }, 29 | }, 30 | { 31 | Input: `{ 32 | "portals": [{ 33 | "tag": "test", 34 | "domain": "test.v2ray.com" 35 | }] 36 | }`, 37 | Parser: loadJSON(creator), 38 | Output: &reverse.Config{ 39 | PortalConfig: []*reverse.PortalConfig{ 40 | {Tag: "test", Domain: "test.v2ray.com"}, 41 | }, 42 | }, 43 | }, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /tools/control/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | _ "v2ray.com/core/infra/conf/command" 9 | "v2ray.com/core/infra/control" 10 | ) 11 | 12 | func getCommandName() string { 13 | if len(os.Args) > 1 { 14 | return os.Args[1] 15 | } 16 | return "" 17 | } 18 | 19 | func main() { 20 | name := getCommandName() 21 | cmd := control.GetCommand(name) 22 | if cmd == nil { 23 | fmt.Fprintln(os.Stderr, "Unknown command:", name) 24 | fmt.Fprintln(os.Stderr) 25 | 26 | fmt.Println("v2ctl ") 27 | fmt.Println("Available commands:") 28 | control.PrintUsage() 29 | return 30 | } 31 | 32 | if err := cmd.Execute(os.Args[2:]); err != nil { 33 | hasError := false 34 | if err != flag.ErrHelp { 35 | fmt.Fprintln(os.Stderr, err.Error()) 36 | fmt.Fprintln(os.Stderr) 37 | hasError = true 38 | } 39 | 40 | for _, line := range cmd.Description().Usage { 41 | fmt.Println(line) 42 | } 43 | 44 | if hasError { 45 | os.Exit(-1) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tools/conf/general_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "v2ray.com/core/common" 9 | . "v2ray.com/ext/tools/conf" 10 | ) 11 | 12 | func loadJSON(creator func() Buildable) func(string) (proto.Message, error) { 13 | return func(s string) (proto.Message, error) { 14 | instance := creator() 15 | if err := json.Unmarshal([]byte(s), instance); err != nil { 16 | return nil, err 17 | } 18 | return instance.Build() 19 | } 20 | } 21 | 22 | type TestCase struct { 23 | Input string 24 | Parser func(string) (proto.Message, error) 25 | Output proto.Message 26 | } 27 | 28 | func runMultiTestCase(t *testing.T, testCases []TestCase) { 29 | for _, testCase := range testCases { 30 | actual, err := testCase.Parser(testCase.Input) 31 | common.Must(err) 32 | if !proto.Equal(actual, testCase.Output) { 33 | t.Fatalf("Failed in test case:\n%s\nActual:\n%v\nExpected:\n%v", testCase.Input, actual, testCase.Output) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tools/conf/freedom_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/net" 7 | "v2ray.com/core/common/protocol" 8 | "v2ray.com/core/proxy/freedom" 9 | . "v2ray.com/ext/tools/conf" 10 | ) 11 | 12 | func TestFreedomConfig(t *testing.T) { 13 | creator := func() Buildable { 14 | return new(FreedomConfig) 15 | } 16 | 17 | runMultiTestCase(t, []TestCase{ 18 | { 19 | Input: `{ 20 | "domainStrategy": "AsIs", 21 | "timeout": 10, 22 | "redirect": "127.0.0.1:3366", 23 | "userLevel": 1 24 | }`, 25 | Parser: loadJSON(creator), 26 | Output: &freedom.Config{ 27 | DomainStrategy: freedom.Config_AS_IS, 28 | Timeout: 10, 29 | DestinationOverride: &freedom.DestinationOverride{ 30 | Server: &protocol.ServerEndpoint{ 31 | Address: &net.IPOrDomain{ 32 | Address: &net.IPOrDomain_Ip{ 33 | Ip: []byte{127, 0, 0, 1}, 34 | }, 35 | }, 36 | Port: 3366, 37 | }, 38 | }, 39 | UserLevel: 1, 40 | }, 41 | }, 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /tools/control/command.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Description struct { 9 | Short string 10 | Usage []string 11 | } 12 | 13 | type Command interface { 14 | Name() string 15 | Description() Description 16 | Execute(args []string) error 17 | } 18 | 19 | var ( 20 | commandRegistry = make(map[string]Command) 21 | ) 22 | 23 | func RegisterCommand(cmd Command) error { 24 | entry := strings.ToLower(cmd.Name()) 25 | if len(entry) == 0 { 26 | return newError("empty command name") 27 | } 28 | commandRegistry[entry] = cmd 29 | return nil 30 | } 31 | 32 | func GetCommand(name string) Command { 33 | cmd, found := commandRegistry[name] 34 | if !found { 35 | return nil 36 | } 37 | return cmd 38 | } 39 | 40 | type hiddenCommand interface { 41 | Hidden() bool 42 | } 43 | 44 | func PrintUsage() { 45 | for name, cmd := range commandRegistry { 46 | if _, ok := cmd.(hiddenCommand); ok { 47 | continue 48 | } 49 | fmt.Println(" ", name, "\t\t\t", cmd.Description()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2019 V2Ray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/conf/api.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "strings" 5 | 6 | "v2ray.com/core/app/commander" 7 | loggerservice "v2ray.com/core/app/log/command" 8 | handlerservice "v2ray.com/core/app/proxyman/command" 9 | statsservice "v2ray.com/core/app/stats/command" 10 | "v2ray.com/core/common/serial" 11 | ) 12 | 13 | type ApiConfig struct { 14 | Tag string `json:"tag"` 15 | Services []string `json:"services"` 16 | } 17 | 18 | func (c *ApiConfig) Build() (*commander.Config, error) { 19 | if len(c.Tag) == 0 { 20 | return nil, newError("Api tag can't be empty.") 21 | } 22 | 23 | services := make([]*serial.TypedMessage, 0, 16) 24 | for _, s := range c.Services { 25 | switch strings.ToLower(s) { 26 | case "handlerservice": 27 | services = append(services, serial.ToTypedMessage(&handlerservice.Config{})) 28 | case "loggerservice": 29 | services = append(services, serial.ToTypedMessage(&loggerservice.Config{})) 30 | case "statsservice": 31 | services = append(services, serial.ToTypedMessage(&statsservice.Config{})) 32 | } 33 | } 34 | 35 | return &commander.Config{ 36 | Tag: c.Tag, 37 | Service: services, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - template: azure-pipelines.template.yml 3 | parameters: 4 | name: linux 5 | vmImage: 'ubuntu-16.04' 6 | 7 | - template: azure-pipelines.template.yml 8 | parameters: 9 | name: windows 10 | vmImage: 'vs2017-win2016' 11 | 12 | - template: azure-pipelines.template.yml 13 | parameters: 14 | name: macos 15 | vmImage: 'macOS-10.13' 16 | 17 | - job: linux_coverage 18 | dependsOn: linux 19 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) 20 | timeoutInMinutes: 30 21 | 22 | pool: 23 | vmImage: 'Ubuntu 16.04' 24 | 25 | variables: 26 | GOPATH: '$(system.defaultWorkingDirectory)' 27 | CODECOV_TOKEN: '$(coverage.token)' 28 | 29 | steps: 30 | - checkout: none 31 | - script: | 32 | go version 33 | go get -v -t -d v2ray.com/core/... 34 | go get -v -t -d v2ray.com/ext/... 35 | workingDirectory: '$(system.defaultWorkingDirectory)' 36 | displayName: 'Fetch sources' 37 | - script: | 38 | cd ./src/v2ray.com/ext 39 | bash ./testing/coverage/coverall 40 | workingDirectory: '$(system.defaultWorkingDirectory)' 41 | displayName: 'Coverage' 42 | -------------------------------------------------------------------------------- /tools/conf/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | //go:generate errorgen 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "v2ray.com/core/common" 10 | "v2ray.com/ext/tools/conf/serial" 11 | "v2ray.com/ext/tools/control" 12 | ) 13 | 14 | type ConfigCommand struct{} 15 | 16 | func (c *ConfigCommand) Name() string { 17 | return "config" 18 | } 19 | 20 | func (c *ConfigCommand) Description() control.Description { 21 | return control.Description{ 22 | Short: "Convert config among different formats.", 23 | Usage: []string{ 24 | "v2ctl config", 25 | }, 26 | } 27 | } 28 | 29 | func (c *ConfigCommand) Execute(args []string) error { 30 | pbConfig, err := serial.LoadJSONConfig(os.Stdin) 31 | if err != nil { 32 | return newError("failed to parse json config").Base(err) 33 | } 34 | 35 | bytesConfig, err := proto.Marshal(pbConfig) 36 | if err != nil { 37 | return newError("failed to marshal proto config").Base(err) 38 | } 39 | 40 | if _, err := os.Stdout.Write(bytesConfig); err != nil { 41 | return newError("failed to write proto config").Base(err) 42 | } 43 | return nil 44 | } 45 | 46 | func init() { 47 | common.Must(control.RegisterCommand(&ConfigCommand{})) 48 | } 49 | -------------------------------------------------------------------------------- /tools/conf/serial/loader_test.go: -------------------------------------------------------------------------------- 1 | package serial_test 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "v2ray.com/ext/tools/conf/serial" 9 | ) 10 | 11 | func TestLoaderError(t *testing.T) { 12 | testCases := []struct { 13 | Input string 14 | Output string 15 | }{ 16 | { 17 | Input: `{ 18 | "log": { 19 | // abcd 20 | 0, 21 | "loglevel": "info" 22 | } 23 | }`, 24 | Output: "line 4 char 6", 25 | }, 26 | { 27 | Input: `{ 28 | "log": { 29 | // abcd 30 | "loglevel": "info", 31 | } 32 | }`, 33 | Output: "line 5 char 5", 34 | }, 35 | { 36 | Input: `{ 37 | "port": 1, 38 | "inbounds": [{ 39 | "protocol": "test" 40 | }] 41 | }`, 42 | Output: "parse json config", 43 | }, 44 | { 45 | Input: `{ 46 | "inbounds": [{ 47 | "port": 1, 48 | "listen": 0, 49 | "protocol": "test" 50 | }] 51 | }`, 52 | Output: "line 1 char 1", 53 | }, 54 | } 55 | for _, testCase := range testCases { 56 | reader := bytes.NewReader([]byte(testCase.Input)) 57 | _, err := serial.LoadJSONConfig(reader) 58 | errString := err.Error() 59 | if !strings.Contains(errString, testCase.Output) { 60 | t.Error("unexpected output from json: ", testCase.Input, ". expected ", testCase.Output, ", but actually ", errString) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tools/conf/reverse.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "v2ray.com/core/app/reverse" 6 | ) 7 | 8 | type BridgeConfig struct { 9 | Tag string `json:"tag"` 10 | Domain string `json:"domain"` 11 | } 12 | 13 | func (c *BridgeConfig) Build() (*reverse.BridgeConfig, error) { 14 | return &reverse.BridgeConfig{ 15 | Tag: c.Tag, 16 | Domain: c.Domain, 17 | }, nil 18 | } 19 | 20 | type PortalConfig struct { 21 | Tag string `json:"tag"` 22 | Domain string `json:"domain"` 23 | } 24 | 25 | func (c *PortalConfig) Build() (*reverse.PortalConfig, error) { 26 | return &reverse.PortalConfig{ 27 | Tag: c.Tag, 28 | Domain: c.Domain, 29 | }, nil 30 | } 31 | 32 | type ReverseConfig struct { 33 | Bridges []BridgeConfig `json:"bridges"` 34 | Portals []PortalConfig `json:"portals"` 35 | } 36 | 37 | func (c *ReverseConfig) Build() (proto.Message, error) { 38 | config := &reverse.Config{} 39 | for _, bconfig := range c.Bridges { 40 | b, err := bconfig.Build() 41 | if err != nil { 42 | return nil, err 43 | } 44 | config.BridgeConfig = append(config.BridgeConfig, b) 45 | } 46 | 47 | for _, pconfig := range c.Portals { 48 | p, err := pconfig.Build() 49 | if err != nil { 50 | return nil, err 51 | } 52 | config.PortalConfig = append(config.PortalConfig, p) 53 | } 54 | 55 | return config, nil 56 | } 57 | -------------------------------------------------------------------------------- /tools/conf/blackhole.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/golang/protobuf/proto" 7 | 8 | "v2ray.com/core/common/serial" 9 | "v2ray.com/core/proxy/blackhole" 10 | ) 11 | 12 | type NoneResponse struct{} 13 | 14 | func (*NoneResponse) Build() (proto.Message, error) { 15 | return new(blackhole.NoneResponse), nil 16 | } 17 | 18 | type HttpResponse struct{} 19 | 20 | func (*HttpResponse) Build() (proto.Message, error) { 21 | return new(blackhole.HTTPResponse), nil 22 | } 23 | 24 | type BlackholeConfig struct { 25 | Response json.RawMessage `json:"response"` 26 | } 27 | 28 | func (v *BlackholeConfig) Build() (proto.Message, error) { 29 | config := new(blackhole.Config) 30 | if v.Response != nil { 31 | response, _, err := configLoader.Load(v.Response) 32 | if err != nil { 33 | return nil, newError("Config: Failed to parse Blackhole response config.").Base(err) 34 | } 35 | responseSettings, err := response.(Buildable).Build() 36 | if err != nil { 37 | return nil, err 38 | } 39 | config.Response = serial.ToTypedMessage(responseSettings) 40 | } 41 | 42 | return config, nil 43 | } 44 | 45 | var ( 46 | configLoader = NewJSONConfigLoader( 47 | ConfigCreatorCache{ 48 | "none": func() interface{} { return new(NoneResponse) }, 49 | "http": func() interface{} { return new(HttpResponse) }, 50 | }, 51 | "type", 52 | "") 53 | ) 54 | -------------------------------------------------------------------------------- /tools/conf/log.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "strings" 5 | 6 | "v2ray.com/core/app/log" 7 | clog "v2ray.com/core/common/log" 8 | ) 9 | 10 | func DefaultLogConfig() *log.Config { 11 | return &log.Config{ 12 | AccessLogType: log.LogType_None, 13 | ErrorLogType: log.LogType_Console, 14 | ErrorLogLevel: clog.Severity_Warning, 15 | } 16 | } 17 | 18 | type LogConfig struct { 19 | AccessLog string `json:"access"` 20 | ErrorLog string `json:"error"` 21 | LogLevel string `json:"loglevel"` 22 | } 23 | 24 | func (v *LogConfig) Build() *log.Config { 25 | if v == nil { 26 | return nil 27 | } 28 | config := &log.Config{ 29 | ErrorLogType: log.LogType_Console, 30 | AccessLogType: log.LogType_Console, 31 | } 32 | 33 | if len(v.AccessLog) > 0 { 34 | config.AccessLogPath = v.AccessLog 35 | config.AccessLogType = log.LogType_File 36 | } 37 | if len(v.ErrorLog) > 0 { 38 | config.ErrorLogPath = v.ErrorLog 39 | config.ErrorLogType = log.LogType_File 40 | } 41 | 42 | level := strings.ToLower(v.LogLevel) 43 | switch level { 44 | case "debug": 45 | config.ErrorLogLevel = clog.Severity_Debug 46 | case "info": 47 | config.ErrorLogLevel = clog.Severity_Info 48 | case "error": 49 | config.ErrorLogLevel = clog.Severity_Error 50 | case "none": 51 | config.ErrorLogType = log.LogType_None 52 | config.AccessLogType = log.LogType_None 53 | default: 54 | config.ErrorLogLevel = clog.Severity_Warning 55 | } 56 | return config 57 | } 58 | -------------------------------------------------------------------------------- /tools/control/main/targets.bzl: -------------------------------------------------------------------------------- 1 | load("//bazel:build.bzl", "foreign_go_binary") 2 | load("//bazel:gpg.bzl", "gpg_sign") 3 | 4 | def gen_targets(matrix): 5 | output = "v2ctl" 6 | pkg = "v2ray.com/ext/tools/control/main" 7 | 8 | for (os, arch) in matrix: 9 | bin_name = "v2ctl_" + os + "_" + arch 10 | foreign_go_binary( 11 | name = bin_name, 12 | pkg = pkg, 13 | output = output, 14 | os = os, 15 | arch = arch, 16 | gotags = "confonly", 17 | ) 18 | 19 | gpg_sign( 20 | name = bin_name + "_sig", 21 | base = ":" + bin_name, 22 | ) 23 | 24 | if arch in ["mips", "mipsle"]: 25 | bin_name = "v2ctl_" + os + "_" + arch + "_softfloat" 26 | foreign_go_binary( 27 | name = bin_name, 28 | pkg = pkg, 29 | output = output + "_softfloat", 30 | os = os, 31 | arch = arch, 32 | mips = "softfloat", 33 | gotags = "confonly", 34 | ) 35 | 36 | gpg_sign( 37 | name = bin_name + "_sig", 38 | base = ":" + bin_name, 39 | ) 40 | 41 | if arch in ["arm"]: 42 | bin_name = "v2ctl_" + os + "_" + arch + "_armv7" 43 | foreign_go_binary( 44 | name = bin_name, 45 | pkg = pkg, 46 | output = output + "_armv7", 47 | os = os, 48 | arch = arch, 49 | arm = "7", 50 | gotags = "confonly", 51 | ) 52 | 53 | gpg_sign( 54 | name = bin_name + "_sig", 55 | base = ":" + bin_name, 56 | ) 57 | -------------------------------------------------------------------------------- /testing/coverage/coverall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -n "${TRAVIS_TAG}" ]; then 4 | exit 0 5 | fi 6 | 7 | FAIL=0 8 | 9 | V2RAY_OUT=${GOPATH}/out/v2ray 10 | V2RAY_COV=${V2RAY_OUT}/cov 11 | COVERAGE_FILE=${V2RAY_COV}/coverage.txt 12 | 13 | function test_package { 14 | DIR="v2ray.com/ext/$1" 15 | DEP=$(go list -f '{{ join .Deps "\n" }}' $DIR | grep v2ray | tr '\n' ',') 16 | DEP=${DEP}$DIR 17 | RND_NAME=$(openssl rand -hex 16) 18 | COV_PROFILE=${V2RAY_COV}/${RND_NAME}.out 19 | go test -tags "json coverage" -coverprofile=${COV_PROFILE} -coverpkg=$DEP $DIR || FAIL=1 20 | } 21 | 22 | rm -rf ${V2RAY_OUT} 23 | mkdir -p ${V2RAY_COV} 24 | touch ${COVERAGE_FILE} 25 | 26 | TEST_FILES=(./*_test.go) 27 | if [ -f ${TEST_FILES[0]} ]; then 28 | test_package "" 29 | fi 30 | 31 | for DIR in $(find * -type d -not -path "*.git*"); do 32 | TEST_FILES=($DIR/*_test.go) 33 | if [ -f ${TEST_FILES[0]} ]; then 34 | test_package $DIR 35 | fi 36 | done 37 | 38 | for OUT_FILE in $(find ${V2RAY_COV} -name "*.out"); do 39 | echo "Merging file ${OUT_FILE}" 40 | cat ${OUT_FILE} | grep -v "mode: set" >> ${COVERAGE_FILE} 41 | done 42 | 43 | COV_SORTED=${V2RAY_COV}/coverallsorted.out 44 | cat ${COVERAGE_FILE} | sort -t: -k1 | grep -vw "testing" | grep -v ".pb.go" | grep -vw "vendor" > ${COV_SORTED} 45 | echo "mode: set" | cat - ${COV_SORTED} > ${COVERAGE_FILE} 46 | 47 | if [ "$FAIL" -eq 0 ]; then 48 | echo "Uploading coverage datea to codecov." 49 | bash <(curl -s https://codecov.io/bash) -f ${COVERAGE_FILE} -v || echo "Codecov did not collect coverage reports." 50 | fi 51 | 52 | exit $FAIL 53 | -------------------------------------------------------------------------------- /tools/control/fetch.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "os" 7 | "strings" 8 | 9 | "v2ray.com/core/common" 10 | "v2ray.com/core/common/buf" 11 | ) 12 | 13 | type FetchCommand struct{} 14 | 15 | func (c *FetchCommand) Name() string { 16 | return "fetch" 17 | } 18 | 19 | func (c *FetchCommand) Description() Description { 20 | return Description{ 21 | Short: "Fetch resources", 22 | Usage: []string{"v2ctl fetch "}, 23 | } 24 | } 25 | 26 | func (c *FetchCommand) isValidScheme(scheme string) bool { 27 | scheme = strings.ToLower(scheme) 28 | return scheme == "http" || scheme == "https" 29 | } 30 | 31 | func (c *FetchCommand) Execute(args []string) error { 32 | if len(args) < 1 { 33 | return newError("empty url") 34 | } 35 | target := args[0] 36 | parsedTarget, err := url.Parse(target) 37 | if err != nil { 38 | return newError("invalid URL: ", target).Base(err) 39 | } 40 | if !c.isValidScheme(parsedTarget.Scheme) { 41 | return newError("invalid scheme: ", parsedTarget.Scheme) 42 | } 43 | 44 | client := &http.Client{} 45 | resp, err := client.Do(&http.Request{ 46 | Method: "GET", 47 | URL: parsedTarget, 48 | Close: true, 49 | }) 50 | if err != nil { 51 | return newError("failed to dial to ", target).Base(err) 52 | } 53 | 54 | if resp.StatusCode != 200 { 55 | return newError("unexpected HTTP status code: ", resp.StatusCode) 56 | } 57 | 58 | content, err := buf.ReadAllToBytes(resp.Body) 59 | if err != nil { 60 | return newError("failed to read HTTP response").Base(err) 61 | } 62 | 63 | os.Stdout.Write(content) 64 | 65 | return nil 66 | } 67 | 68 | func init() { 69 | common.Must(RegisterCommand(&FetchCommand{})) 70 | } 71 | -------------------------------------------------------------------------------- /tools/conf/serial/loader.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | 8 | "v2ray.com/core" 9 | "v2ray.com/core/common/errors" 10 | json_reader "v2ray.com/ext/encoding/json" 11 | "v2ray.com/ext/tools/conf" 12 | ) 13 | 14 | type offset struct { 15 | line int 16 | char int 17 | } 18 | 19 | func findOffset(b []byte, o int) *offset { 20 | if o >= len(b) || o < 0 { 21 | return nil 22 | } 23 | 24 | line := 1 25 | char := 0 26 | for i, x := range b { 27 | if i == o { 28 | break 29 | } 30 | if x == '\n' { 31 | line++ 32 | char = 0 33 | } else { 34 | char++ 35 | } 36 | } 37 | 38 | return &offset{line: line, char: char} 39 | } 40 | 41 | func LoadJSONConfig(reader io.Reader) (*core.Config, error) { 42 | jsonConfig := &conf.Config{} 43 | 44 | jsonContent := bytes.NewBuffer(make([]byte, 0, 10240)) 45 | jsonReader := io.TeeReader(&json_reader.Reader{ 46 | Reader: reader, 47 | }, jsonContent) 48 | decoder := json.NewDecoder(jsonReader) 49 | 50 | if err := decoder.Decode(jsonConfig); err != nil { 51 | var pos *offset 52 | cause := errors.Cause(err) 53 | switch tErr := cause.(type) { 54 | case *json.SyntaxError: 55 | pos = findOffset(jsonContent.Bytes(), int(tErr.Offset)) 56 | case *json.UnmarshalTypeError: 57 | pos = findOffset(jsonContent.Bytes(), int(tErr.Offset)) 58 | } 59 | if pos != nil { 60 | return nil, newError("failed to read config file at line ", pos.line, " char ", pos.char).Base(err) 61 | } 62 | return nil, newError("failed to read config file").Base(err) 63 | } 64 | 65 | pbConfig, err := jsonConfig.Build() 66 | if err != nil { 67 | return nil, newError("failed to parse json config").Base(err) 68 | } 69 | 70 | return pbConfig, nil 71 | } 72 | -------------------------------------------------------------------------------- /tools/conf/freedom.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/golang/protobuf/proto" 8 | v2net "v2ray.com/core/common/net" 9 | "v2ray.com/core/common/protocol" 10 | "v2ray.com/core/proxy/freedom" 11 | ) 12 | 13 | type FreedomConfig struct { 14 | DomainStrategy string `json:"domainStrategy"` 15 | Timeout *uint32 `json:"timeout"` 16 | Redirect string `json:"redirect"` 17 | UserLevel uint32 `json:"userLevel"` 18 | } 19 | 20 | // Build implements Buildable 21 | func (c *FreedomConfig) Build() (proto.Message, error) { 22 | config := new(freedom.Config) 23 | config.DomainStrategy = freedom.Config_AS_IS 24 | switch strings.ToLower(c.DomainStrategy) { 25 | case "useip", "use_ip": 26 | config.DomainStrategy = freedom.Config_USE_IP 27 | case "useip4", "useipv4", "use_ipv4", "use_ip_v4", "use_ip4": 28 | config.DomainStrategy = freedom.Config_USE_IP4 29 | case "useip6", "useipv6", "use_ipv6", "use_ip_v6", "use_ip6": 30 | config.DomainStrategy = freedom.Config_USE_IP6 31 | } 32 | config.Timeout = 600 33 | if c.Timeout != nil { 34 | config.Timeout = *c.Timeout 35 | } 36 | config.UserLevel = c.UserLevel 37 | if len(c.Redirect) > 0 { 38 | host, portStr, err := net.SplitHostPort(c.Redirect) 39 | if err != nil { 40 | return nil, newError("invalid redirect address: ", c.Redirect, ": ", err).Base(err) 41 | } 42 | port, err := v2net.PortFromString(portStr) 43 | if err != nil { 44 | return nil, newError("invalid redirect port: ", c.Redirect, ": ", err).Base(err) 45 | } 46 | config.DestinationOverride = &freedom.DestinationOverride{ 47 | Server: &protocol.ServerEndpoint{ 48 | Port: uint32(port), 49 | }, 50 | } 51 | 52 | if len(host) > 0 { 53 | config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host)) 54 | } 55 | } 56 | return config, nil 57 | } 58 | -------------------------------------------------------------------------------- /bazel/build.bzl: -------------------------------------------------------------------------------- 1 | def _go_command(ctx): 2 | output = ctx.attr.output 3 | if ctx.attr.os == "windows": 4 | output = output + ".exe" 5 | 6 | output_file = ctx.actions.declare_file(ctx.attr.os + "/" + ctx.attr.arch + "/" + output) 7 | pkg = ctx.attr.pkg 8 | 9 | ld_flags = "-s -w" 10 | if ctx.attr.ld: 11 | ld_flags = ld_flags + " " + ctx.attr.ld 12 | 13 | options = [ 14 | "go", 15 | "build", 16 | "-o", output_file.path, 17 | "-compiler", "gc", 18 | "-gcflags", '"all=-trimpath=${GOPATH}/src"', 19 | "-asmflags", '"all=-trimpath=${GOPATH}/src"', 20 | "-ldflags", "'%s'" % ld_flags, 21 | "-tags", "'%s'" % ctx.attr.gotags, 22 | pkg, 23 | ] 24 | 25 | command = " ".join(options) 26 | 27 | envs = [ 28 | "CGO_ENABLED=0", 29 | "GOOS="+ctx.attr.os, 30 | "GOARCH="+ctx.attr.arch, 31 | "GOROOT_FINAL=/go" 32 | ] 33 | 34 | if ctx.attr.mips: # https://github.com/golang/go/issues/27260 35 | envs+=["GOMIPS="+ctx.attr.mips] 36 | envs+=["GOMIPS64="+ctx.attr.mips] 37 | envs+=["GOMIPSLE="+ctx.attr.mips] 38 | envs+=["GOMIPS64LE="+ctx.attr.mips] 39 | if ctx.attr.arm: 40 | envs+=["GOARM="+ctx.attr.arm] 41 | 42 | command = " ".join(envs) + " " + command 43 | 44 | ctx.actions.run_shell( 45 | outputs = [output_file], 46 | command = command, 47 | use_default_shell_env = True, 48 | ) 49 | runfiles = ctx.runfiles(files = [output_file]) 50 | return [DefaultInfo(executable = output_file, runfiles = runfiles)] 51 | 52 | 53 | foreign_go_binary = rule( 54 | _go_command, 55 | attrs = { 56 | 'pkg': attr.string(), 57 | 'output': attr.string(), 58 | 'os': attr.string(mandatory=True), 59 | 'arch': attr.string(mandatory=True), 60 | 'mips': attr.string(), 61 | 'arm': attr.string(), 62 | 'ld': attr.string(), 63 | 'gotags': attr.string(), 64 | }, 65 | executable = True, 66 | ) 67 | -------------------------------------------------------------------------------- /demo/dialer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "v2ray.com/core" 10 | "v2ray.com/core/app/dispatcher" 11 | "v2ray.com/core/app/proxyman" 12 | _ "v2ray.com/core/app/proxyman/outbound" 13 | "v2ray.com/core/common/net" 14 | "v2ray.com/core/common/protocol" 15 | "v2ray.com/core/common/serial" 16 | "v2ray.com/core/proxy/socks" 17 | _ "v2ray.com/core/transport/internet/tcp" 18 | ) 19 | 20 | func main() { 21 | config := &core.Config{ 22 | App: []*serial.TypedMessage{ 23 | serial.ToTypedMessage(&dispatcher.Config{}), 24 | serial.ToTypedMessage(&proxyman.OutboundConfig{}), 25 | }, 26 | Outbound: []*core.OutboundHandlerConfig{{ 27 | ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{ 28 | Server: []*protocol.ServerEndpoint{{ 29 | Address: net.NewIPOrDomain(net.ParseAddress("162.243.108.129")), 30 | Port: 1080, 31 | }}, 32 | })}, 33 | }, 34 | } 35 | 36 | v, err := core.New(config) 37 | if err != nil { 38 | fmt.Println("Failed to create V: ", err.Error()) 39 | os.Exit(-1) 40 | } 41 | 42 | conn, err := core.Dial(context.Background(), v, net.TCPDestination(net.ParseAddress("www.v2ray.com"), net.Port(80))) 43 | if err != nil { 44 | fmt.Println("Failed to dial connection: ", err.Error()) 45 | } 46 | 47 | _, err = conn.Write([]byte(`GET / HTTP/1.1 48 | Host: www.v2ray.com 49 | User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) 50 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 51 | Accept-Language: en-us,en;q=0.5 52 | Accept-Encoding: gzip,deflate 53 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 54 | 55 | `)) 56 | if err != nil { 57 | fmt.Println("Failed to write request: ", err.Error()) 58 | } 59 | 60 | _, err = io.Copy(os.Stdout, conn) 61 | if err != nil { 62 | fmt.Println("Failed to read response: ", err.Error()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tools/conf/mtproto.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "v2ray.com/core/common/protocol" 10 | "v2ray.com/core/common/serial" 11 | "v2ray.com/core/proxy/mtproto" 12 | ) 13 | 14 | type MTProtoAccount struct { 15 | Secret string `json:"secret"` 16 | } 17 | 18 | // Build implements Buildable 19 | func (a *MTProtoAccount) Build() (*mtproto.Account, error) { 20 | if len(a.Secret) != 32 { 21 | return nil, newError("MTProto secret must have 32 chars") 22 | } 23 | secret, err := hex.DecodeString(a.Secret) 24 | if err != nil { 25 | return nil, newError("failed to decode secret: ", a.Secret).Base(err) 26 | } 27 | return &mtproto.Account{ 28 | Secret: secret, 29 | }, nil 30 | } 31 | 32 | type MTProtoServerConfig struct { 33 | Users []json.RawMessage `json:"users"` 34 | } 35 | 36 | func (c *MTProtoServerConfig) Build() (proto.Message, error) { 37 | config := &mtproto.ServerConfig{} 38 | 39 | if len(c.Users) == 0 { 40 | return nil, newError("zero MTProto users configured.") 41 | } 42 | config.User = make([]*protocol.User, len(c.Users)) 43 | for idx, rawData := range c.Users { 44 | user := new(protocol.User) 45 | if err := json.Unmarshal(rawData, user); err != nil { 46 | return nil, newError("invalid MTProto user").Base(err) 47 | } 48 | account := new(MTProtoAccount) 49 | if err := json.Unmarshal(rawData, account); err != nil { 50 | return nil, newError("invalid MTProto user").Base(err) 51 | } 52 | accountProto, err := account.Build() 53 | if err != nil { 54 | return nil, newError("failed to parse MTProto user").Base(err) 55 | } 56 | user.Account = serial.ToTypedMessage(accountProto) 57 | config.User[idx] = user 58 | } 59 | 60 | return config, nil 61 | } 62 | 63 | type MTProtoClientConfig struct { 64 | } 65 | 66 | func (c *MTProtoClientConfig) Build() (proto.Message, error) { 67 | config := new(mtproto.ClientConfig) 68 | return config, nil 69 | } 70 | -------------------------------------------------------------------------------- /tools/control/love.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/gzip" 7 | "encoding/base64" 8 | "fmt" 9 | 10 | "v2ray.com/core/common" 11 | "v2ray.com/core/common/platform" 12 | ) 13 | 14 | const content = "H4sIAAAAAAAC/4SVMaskNwzH+/kUW6izcSthMGrcqLhVk0rdQS5cSMg7Xu4S0vizB8meZd57M3ta2GHX/ukvyZZmY2ZKDMzCzJyY5yOlxKII1omsf+qkBiiC6WhbYsbkjDAfySQsJqD3jtrD0EBM3sBHzG3kUsrglIQREXonpd47kYIi4AHmgI9Wcq2jlJITC6JZJ+v3ECYzBMAHyYm392yuY4zWsjACmHZSh6l3A0JETzGlWZqDsnArpTg62mhJONhOdO90p97V1BAnteoaOcuummtrrtuERQwUiJwP8a4KGKcyxdOCw1spOY+WHueFqmakAIgUSSuhwKNgobxKXSLbtg6r5cFmBiAeF6yCkYycmv+BiCIiW8ScHa3DgxAuZQbRhFNrLTFo96RBmx9jKWWG5nBsjyJzuIkftUblonppZU5t5LzwIks5L1a4lijagQxLokbIYwxfytNDC+XQqrWW9fzAunhqh5/Tg8PuaMw0d/Tcw3iDO81bHfWM/AnutMh2xqSUntMzd3wHDy9iHMQz8bmUZYvqedTJ5GgOnrNt7FIbSlwXE3wDI19n/KA38MsLaP4l89b5F8AV3ESOMIEhIBgezHBc0H6xV9KbaXwMvPcNvIHcC0C7UPZQx4JVTb35/AneSQq+bAYXsBmY7TCRupF2NTdVm/+ch22xa0pvRERKqt1oxj9DUbXzU84Gvj5hc5a81SlAUwMwgEs4T9+7sg9lb9h+908MWiKV8xtWciVTmnB3tivRjNerfXdxpfEBbq2NUvLMM5R9NLuyQg8nXT0PIh1xPd/wrcV49oJ6zbZaPlj2V87IY9T3F2XCOcW2MbZyZd49H+9m81E1N9SxlU+ff/1y+/f3719vf7788+Ugv/ffbMIH7ZNj0dsT4WMHHwLPu/Rp2O75uh99AK+N2xn7ZHq1OK6gczkN+9ngdOl1Qvki5xwSR8vFX6D+9vXA97B/+fr5rz9u/738uP328urP19vfP759e3n9Xs6jamvqlfJ/AAAA//+YAMZjDgkAAA==" 15 | 16 | type LoveCommand struct{} 17 | 18 | func (*LoveCommand) Name() string { 19 | return "lovevictoria" 20 | } 21 | 22 | func (*LoveCommand) Hidden() bool { 23 | return false 24 | } 25 | 26 | func (c *LoveCommand) Description() Description { 27 | return Description{ 28 | Short: "", 29 | Usage: []string{""}, 30 | } 31 | } 32 | 33 | func (*LoveCommand) Execute([]string) error { 34 | c, err := base64.StdEncoding.DecodeString(content) 35 | common.Must(err) 36 | reader, err := gzip.NewReader(bytes.NewBuffer(c)) 37 | common.Must(err) 38 | b := make([]byte, 4096) 39 | nBytes, _ := reader.Read(b) 40 | 41 | bb := bytes.NewBuffer(b[:nBytes]) 42 | scanner := bufio.NewScanner(bb) 43 | for scanner.Scan() { 44 | s := scanner.Text() 45 | fmt.Print(s + platform.LineSeparator()) 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func init() { 52 | common.Must(RegisterCommand(&LoveCommand{})) 53 | } 54 | -------------------------------------------------------------------------------- /tools/conf/socks_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/net" 7 | "v2ray.com/core/common/protocol" 8 | "v2ray.com/core/common/serial" 9 | "v2ray.com/core/proxy/socks" 10 | . "v2ray.com/ext/tools/conf" 11 | ) 12 | 13 | func TestSocksInboundConfig(t *testing.T) { 14 | creator := func() Buildable { 15 | return new(SocksServerConfig) 16 | } 17 | 18 | runMultiTestCase(t, []TestCase{ 19 | { 20 | Input: `{ 21 | "auth": "password", 22 | "accounts": [ 23 | { 24 | "user": "my-username", 25 | "pass": "my-password" 26 | } 27 | ], 28 | "udp": false, 29 | "ip": "127.0.0.1", 30 | "timeout": 5, 31 | "userLevel": 1 32 | }`, 33 | Parser: loadJSON(creator), 34 | Output: &socks.ServerConfig{ 35 | AuthType: socks.AuthType_PASSWORD, 36 | Accounts: map[string]string{ 37 | "my-username": "my-password", 38 | }, 39 | UdpEnabled: false, 40 | Address: &net.IPOrDomain{ 41 | Address: &net.IPOrDomain_Ip{ 42 | Ip: []byte{127, 0, 0, 1}, 43 | }, 44 | }, 45 | Timeout: 5, 46 | UserLevel: 1, 47 | }, 48 | }, 49 | }) 50 | } 51 | 52 | func TestSocksOutboundConfig(t *testing.T) { 53 | creator := func() Buildable { 54 | return new(SocksClientConfig) 55 | } 56 | 57 | runMultiTestCase(t, []TestCase{ 58 | { 59 | Input: `{ 60 | "servers": [{ 61 | "address": "127.0.0.1", 62 | "port": 1234, 63 | "users": [ 64 | {"user": "test user", "pass": "test pass", "email": "test@email.com"} 65 | ] 66 | }] 67 | }`, 68 | Parser: loadJSON(creator), 69 | Output: &socks.ClientConfig{ 70 | Server: []*protocol.ServerEndpoint{ 71 | { 72 | Address: &net.IPOrDomain{ 73 | Address: &net.IPOrDomain_Ip{ 74 | Ip: []byte{127, 0, 0, 1}, 75 | }, 76 | }, 77 | Port: 1234, 78 | User: []*protocol.User{ 79 | { 80 | Email: "test@email.com", 81 | Account: serial.ToTypedMessage(&socks.Account{ 82 | Username: "test user", 83 | Password: "test pass", 84 | }), 85 | }, 86 | }, 87 | }, 88 | }, 89 | }, 90 | }, 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /tools/conf/loader.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | ) 7 | 8 | type ConfigCreator func() interface{} 9 | 10 | type ConfigCreatorCache map[string]ConfigCreator 11 | 12 | func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error { 13 | if _, found := v[id]; found { 14 | return newError(id, " already registered.").AtError() 15 | } 16 | 17 | v[id] = creator 18 | return nil 19 | } 20 | 21 | func (v ConfigCreatorCache) CreateConfig(id string) (interface{}, error) { 22 | creator, found := v[id] 23 | if !found { 24 | return nil, newError("unknown config id: ", id) 25 | } 26 | return creator(), nil 27 | } 28 | 29 | type JSONConfigLoader struct { 30 | cache ConfigCreatorCache 31 | idKey string 32 | configKey string 33 | } 34 | 35 | func NewJSONConfigLoader(cache ConfigCreatorCache, idKey string, configKey string) *JSONConfigLoader { 36 | return &JSONConfigLoader{ 37 | idKey: idKey, 38 | configKey: configKey, 39 | cache: cache, 40 | } 41 | } 42 | 43 | func (v *JSONConfigLoader) LoadWithID(raw []byte, id string) (interface{}, error) { 44 | id = strings.ToLower(id) 45 | config, err := v.cache.CreateConfig(id) 46 | if err != nil { 47 | return nil, err 48 | } 49 | if err := json.Unmarshal(raw, config); err != nil { 50 | return nil, err 51 | } 52 | return config, nil 53 | } 54 | 55 | func (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) { 56 | var obj map[string]json.RawMessage 57 | if err := json.Unmarshal(raw, &obj); err != nil { 58 | return nil, "", err 59 | } 60 | rawID, found := obj[v.idKey] 61 | if !found { 62 | return nil, "", newError(v.idKey, " not found in JSON context").AtError() 63 | } 64 | var id string 65 | if err := json.Unmarshal(rawID, &id); err != nil { 66 | return nil, "", err 67 | } 68 | rawConfig := json.RawMessage(raw) 69 | if len(v.configKey) > 0 { 70 | configValue, found := obj[v.configKey] 71 | if found { 72 | rawConfig = configValue 73 | } else { 74 | // Default to empty json object. 75 | rawConfig = json.RawMessage([]byte("{}")) 76 | } 77 | } 78 | config, err := v.LoadWithID([]byte(rawConfig), id) 79 | if err != nil { 80 | return nil, id, err 81 | } 82 | return config, id, nil 83 | } 84 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: v2ray-core 2 | version: '4.2' 3 | summary: An unified platform for anti-censorship. 4 | description: | 5 | V2Ray helps to build your own private network. It supports various proxy protocols such as Socks5, HTTP, VMess and Shadowsocks. 6 | It also provides internal routing mechansim to dynamically accelarate your connection. 7 | 8 | grade: stable 9 | confinement: strict 10 | 11 | parts: 12 | core: 13 | plugin: nil 14 | stage-packages: 15 | - curl 16 | - unzip 17 | build: | 18 | set -x 19 | ARCH=$(uname -m) 20 | VDIS=64 21 | 22 | if [ "$ARCH" = "i686" ] || [ "$ARCH" = "i386" ]; then 23 | VDIS="32" 24 | elif [ "$ARCH" = "armv7l" ] || [ "$ARCH" = "armv6l" ]; then 25 | VDIS="arm" 26 | elif [ "$ARCH" = *"armv8"* ] || [ "$ARCH" = "aarch64" ]; then 27 | VDIS="arm64" 28 | elif [ "$ARCH" = *"mips64le"* ]; then 29 | VDIS="mips64le" 30 | elif [ "$ARCH" = *"mips64"* ]; then 31 | VDIS="mips64" 32 | elif [ "$ARCH" = *"mipsle"* ]; then 33 | VDIS="mipsle" 34 | elif [ "$ARCH" = *"mips"* ]; then 35 | VDIS="mips" 36 | elif [[ "$ARCH" == "ppc64le" ]]; then 37 | VDIS="ppc64le" 38 | elif [[ "$ARCH" == "ppc64" ]]; then 39 | VDIS="ppc64" 40 | fi 41 | URL="https://github.com/v2ray/v2ray-core/releases/download/v${SNAPCRAFT_PROJECT_VERSION}/v2ray-linux-${VDIS}.zip" 42 | curl -L -o v2ray.zip "$URL" 43 | mkdir -p /tmp/v2ray/ 44 | unzip v2ray.zip -d /tmp/v2ray/ 45 | cp /tmp/v2ray/v2ray $SNAPCRAFT_PART_INSTALL 46 | cp /tmp/v2ray/v2ctl $SNAPCRAFT_PART_INSTALL 47 | cp /tmp/v2ray/geoip.dat $SNAPCRAFT_PART_INSTALL 48 | cp /tmp/v2ray/geosite.dat $SNAPCRAFT_PART_INSTALL 49 | cp /tmp/v2ray/vpoint_socks_vmess.json $SNAPCRAFT_PART_INSTALL/config.json 50 | 51 | organize: 52 | v2ray: bin/v2ray 53 | v2ctl: bin/v2ctl 54 | geoip.dat: bin/geoip.dat 55 | geosite.dat: bin/geosite.dat 56 | config.json: bin/config.json 57 | prime: 58 | - bin 59 | 60 | apps: 61 | v2ray: 62 | command: bin/v2ray 63 | daemon: simple 64 | plugs: 65 | - network 66 | - network-bind 67 | - home 68 | v2ctl: 69 | command: bin/v2ctl 70 | plugs: 71 | - home 72 | -------------------------------------------------------------------------------- /encoding/json/reader_test.go: -------------------------------------------------------------------------------- 1 | package json_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | 10 | "v2ray.com/core/common" 11 | . "v2ray.com/ext/encoding/json" 12 | ) 13 | 14 | func TestReader(t *testing.T) { 15 | data := []struct { 16 | input string 17 | output string 18 | }{ 19 | { 20 | ` 21 | content #comment 1 22 | #comment 2 23 | content 2`, 24 | ` 25 | content 26 | 27 | content 2`}, 28 | {`content`, `content`}, 29 | {" ", " "}, 30 | {`con/*abcd*/tent`, "content"}, 31 | {` 32 | text // adlkhdf /* 33 | //comment adfkj 34 | text 2*/`, ` 35 | text 36 | 37 | text 2*`}, 38 | {`"//"content`, `"//"content`}, 39 | {`abcd'//'abcd`, `abcd'//'abcd`}, 40 | {`"\""`, `"\""`}, 41 | {`\"/*abcd*/\"`, `\"\"`}, 42 | } 43 | 44 | for _, testCase := range data { 45 | reader := &Reader{ 46 | Reader: bytes.NewReader([]byte(testCase.input)), 47 | } 48 | 49 | actual := make([]byte, 1024) 50 | n, err := reader.Read(actual) 51 | common.Must(err) 52 | if r := cmp.Diff(string(actual[:n]), testCase.output); r != "" { 53 | t.Error(r) 54 | } 55 | } 56 | } 57 | 58 | func TestReader1(t *testing.T) { 59 | type dataStruct struct { 60 | input string 61 | output string 62 | } 63 | 64 | bufLen := 8 65 | 66 | data := []dataStruct{ 67 | {"loooooooooooooooooooooooooooooooooooooooog", "loooooooooooooooooooooooooooooooooooooooog"}, 68 | {`{"t": "\/testlooooooooooooooooooooooooooooong"}`, `{"t": "\/testlooooooooooooooooooooooooooooong"}`}, 69 | {`{"t": "\/test"}`, `{"t": "\/test"}`}, 70 | {`"\// fake comment"`, `"\// fake comment"`}, 71 | {`"\/\/\/\/\/"`, `"\/\/\/\/\/"`}, 72 | } 73 | 74 | for _, testCase := range data { 75 | reader := &Reader{ 76 | Reader: bytes.NewReader([]byte(testCase.input)), 77 | } 78 | target := make([]byte, 0) 79 | buf := make([]byte, bufLen) 80 | var n int 81 | var err error 82 | for n, err = reader.Read(buf); err == nil; n, err = reader.Read(buf) { 83 | if n > len(buf) { 84 | t.Error("n: ", n) 85 | } 86 | target = append(target, buf[:n]...) 87 | buf = make([]byte, bufLen) 88 | } 89 | if err != nil && err != io.EOF { 90 | t.Error("error: ", err) 91 | } 92 | if string(target) != testCase.output { 93 | t.Error("got ", string(target), " want ", testCase.output) 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /tools/conf/policy.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "v2ray.com/core/app/policy" 5 | ) 6 | 7 | type Policy struct { 8 | Handshake *uint32 `json:"handshake"` 9 | ConnectionIdle *uint32 `json:"connIdle"` 10 | UplinkOnly *uint32 `json:"uplinkOnly"` 11 | DownlinkOnly *uint32 `json:"downlinkOnly"` 12 | StatsUserUplink bool `json:"statsUserUplink"` 13 | StatsUserDownlink bool `json:"statsUserDownlink"` 14 | BufferSize *int32 `json:"bufferSize"` 15 | } 16 | 17 | func (t *Policy) Build() (*policy.Policy, error) { 18 | config := new(policy.Policy_Timeout) 19 | if t.Handshake != nil { 20 | config.Handshake = &policy.Second{Value: *t.Handshake} 21 | } 22 | if t.ConnectionIdle != nil { 23 | config.ConnectionIdle = &policy.Second{Value: *t.ConnectionIdle} 24 | } 25 | if t.UplinkOnly != nil { 26 | config.UplinkOnly = &policy.Second{Value: *t.UplinkOnly} 27 | } 28 | if t.DownlinkOnly != nil { 29 | config.DownlinkOnly = &policy.Second{Value: *t.DownlinkOnly} 30 | } 31 | 32 | p := &policy.Policy{ 33 | Timeout: config, 34 | Stats: &policy.Policy_Stats{ 35 | UserUplink: t.StatsUserUplink, 36 | UserDownlink: t.StatsUserDownlink, 37 | }, 38 | } 39 | 40 | if t.BufferSize != nil { 41 | bs := int32(-1) 42 | if *t.BufferSize >= 0 { 43 | bs = (*t.BufferSize) * 1024 44 | } 45 | p.Buffer = &policy.Policy_Buffer{ 46 | Connection: bs, 47 | } 48 | } 49 | 50 | return p, nil 51 | } 52 | 53 | type SystemPolicy struct { 54 | StatsInboundUplink bool `json:"statsInboundUplink"` 55 | StatsInboundDownlink bool `json:"statsInboundDownlink"` 56 | } 57 | 58 | func (p *SystemPolicy) Build() (*policy.SystemPolicy, error) { 59 | return &policy.SystemPolicy{ 60 | Stats: &policy.SystemPolicy_Stats{ 61 | InboundUplink: p.StatsInboundUplink, 62 | InboundDownlink: p.StatsInboundDownlink, 63 | }, 64 | }, nil 65 | } 66 | 67 | type PolicyConfig struct { 68 | Levels map[uint32]*Policy `json:"levels"` 69 | System *SystemPolicy `json:"system"` 70 | } 71 | 72 | func (c *PolicyConfig) Build() (*policy.Config, error) { 73 | levels := make(map[uint32]*policy.Policy) 74 | for l, p := range c.Levels { 75 | if p != nil { 76 | pp, err := p.Build() 77 | if err != nil { 78 | return nil, err 79 | } 80 | levels[l] = pp 81 | } 82 | } 83 | config := &policy.Config{ 84 | Level: levels, 85 | } 86 | 87 | if c.System != nil { 88 | sc, err := c.System.Build() 89 | if err != nil { 90 | return nil, err 91 | } 92 | config.System = sc 93 | } 94 | 95 | return config, nil 96 | } 97 | -------------------------------------------------------------------------------- /tools/vprotogen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "runtime" 12 | "strings" 13 | 14 | "v2ray.com/core/common" 15 | ) 16 | 17 | var protocMap = map[string]string{ 18 | "windows": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "windows", "protoc.exe"), 19 | "darwin": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "macos", "protoc"), 20 | "linux": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "linux", "protoc"), 21 | } 22 | 23 | var ( 24 | repo = flag.String("repo", "", "Repo for protobuf generation, such as v2ray.com/core") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | 30 | protofiles := make(map[string][]string) 31 | protoc := protocMap[runtime.GOOS] 32 | gosrc := filepath.Join(os.Getenv("GOPATH"), "src") 33 | reporoot := filepath.Join(os.Getenv("GOPATH"), "src", *repo) 34 | 35 | filepath.Walk(reporoot, func(path string, info os.FileInfo, err error) error { 36 | if err != nil { 37 | fmt.Println(err) 38 | return err 39 | } 40 | 41 | if info.IsDir() { 42 | return nil 43 | } 44 | 45 | dir := filepath.Dir(path) 46 | filename := filepath.Base(path) 47 | if strings.HasSuffix(filename, ".proto") { 48 | protofiles[dir] = append(protofiles[dir], path) 49 | } 50 | 51 | return nil 52 | }) 53 | 54 | for _, files := range protofiles { 55 | args := []string{"--proto_path", gosrc, "--go_out", "plugins=grpc:" + gosrc} 56 | args = append(args, files...) 57 | cmd := exec.Command(protoc, args...) 58 | cmd.Env = append(cmd.Env, os.Environ()...) 59 | output, err := cmd.CombinedOutput() 60 | if len(output) > 0 { 61 | fmt.Println(string(output)) 62 | } 63 | if err != nil { 64 | fmt.Println(err) 65 | } 66 | } 67 | 68 | common.Must(filepath.Walk(reporoot, func(path string, info os.FileInfo, err error) error { 69 | if err != nil { 70 | fmt.Println(err) 71 | return err 72 | } 73 | 74 | if info.IsDir() { 75 | return nil 76 | } 77 | 78 | if !strings.HasSuffix(info.Name(), ".pb.go") { 79 | return nil 80 | } 81 | 82 | content, err := ioutil.ReadFile(path) 83 | if err != nil { 84 | return err 85 | } 86 | content = bytes.Replace(content, []byte("\"golang.org/x/net/context\""), []byte("\"context\""), 1) 87 | 88 | pos := bytes.Index(content, []byte("\npackage")) 89 | if pos > 0 { 90 | content = content[pos+1:] 91 | } 92 | 93 | return ioutil.WriteFile(path, content, info.Mode()) 94 | })) 95 | } 96 | -------------------------------------------------------------------------------- /tools/conf/dns_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/golang/protobuf/proto" 10 | "v2ray.com/core/app/dns" 11 | "v2ray.com/core/common" 12 | "v2ray.com/core/common/net" 13 | "v2ray.com/core/common/platform" 14 | "v2ray.com/ext/sysio" 15 | . "v2ray.com/ext/tools/conf" 16 | ) 17 | 18 | func TestDnsConfigParsing(t *testing.T) { 19 | geositePath := platform.GetAssetLocation("geosite.dat") 20 | common.Must(sysio.CopyFile(geositePath, filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geosite.dat"))) 21 | defer func() { 22 | os.Remove(geositePath) 23 | }() 24 | 25 | parserCreator := func() func(string) (proto.Message, error) { 26 | return func(s string) (proto.Message, error) { 27 | config := new(DnsConfig) 28 | if err := json.Unmarshal([]byte(s), config); err != nil { 29 | return nil, err 30 | } 31 | return config.Build() 32 | } 33 | } 34 | 35 | runMultiTestCase(t, []TestCase{ 36 | { 37 | Input: `{ 38 | "servers": [{ 39 | "address": "8.8.8.8", 40 | "port": 5353, 41 | "domains": ["domain:v2ray.com"] 42 | }], 43 | "hosts": { 44 | "v2ray.com": "127.0.0.1", 45 | "geosite:tld-cn": "10.0.0.1", 46 | "domain:example.com": "google.com" 47 | }, 48 | "clientIp": "10.0.0.1" 49 | }`, 50 | Parser: parserCreator(), 51 | Output: &dns.Config{ 52 | NameServer: []*dns.NameServer{ 53 | { 54 | Address: &net.Endpoint{ 55 | Address: &net.IPOrDomain{ 56 | Address: &net.IPOrDomain_Ip{ 57 | Ip: []byte{8, 8, 8, 8}, 58 | }, 59 | }, 60 | Network: net.Network_UDP, 61 | Port: 5353, 62 | }, 63 | PrioritizedDomain: []*dns.NameServer_PriorityDomain{ 64 | { 65 | Type: dns.DomainMatchingType_Subdomain, 66 | Domain: "v2ray.com", 67 | }, 68 | }, 69 | }, 70 | }, 71 | StaticHosts: []*dns.Config_HostMapping{ 72 | { 73 | Type: dns.DomainMatchingType_Subdomain, 74 | Domain: "example.com", 75 | ProxiedDomain: "google.com", 76 | }, 77 | { 78 | Type: dns.DomainMatchingType_Subdomain, 79 | Domain: "cn", 80 | Ip: [][]byte{{10, 0, 0, 1}}, 81 | }, 82 | { 83 | Type: dns.DomainMatchingType_Subdomain, 84 | Domain: "xn--fiqs8s", 85 | Ip: [][]byte{{10, 0, 0, 1}}, 86 | }, 87 | { 88 | Type: dns.DomainMatchingType_Full, 89 | Domain: "v2ray.com", 90 | Ip: [][]byte{{127, 0, 0, 1}}, 91 | }, 92 | }, 93 | ClientIp: []byte{10, 0, 0, 1}, 94 | }, 95 | }, 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /tools/conf/transport.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "v2ray.com/core/common/serial" 5 | "v2ray.com/core/transport" 6 | "v2ray.com/core/transport/internet" 7 | ) 8 | 9 | type TransportConfig struct { 10 | TCPConfig *TCPConfig `json:"tcpSettings"` 11 | KCPConfig *KCPConfig `json:"kcpSettings"` 12 | WSConfig *WebSocketConfig `json:"wsSettings"` 13 | HTTPConfig *HTTPConfig `json:"httpSettings"` 14 | DSConfig *DomainSocketConfig `json:"dsSettings"` 15 | QUICConfig *QUICConfig `json:"quicSettings"` 16 | } 17 | 18 | // Build implements Buildable. 19 | func (c *TransportConfig) Build() (*transport.Config, error) { 20 | config := new(transport.Config) 21 | 22 | if c.TCPConfig != nil { 23 | ts, err := c.TCPConfig.Build() 24 | if err != nil { 25 | return nil, newError("failed to build TCP config").Base(err).AtError() 26 | } 27 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 28 | ProtocolName: "tcp", 29 | Settings: serial.ToTypedMessage(ts), 30 | }) 31 | } 32 | 33 | if c.KCPConfig != nil { 34 | ts, err := c.KCPConfig.Build() 35 | if err != nil { 36 | return nil, newError("failed to build mKCP config").Base(err).AtError() 37 | } 38 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 39 | ProtocolName: "mkcp", 40 | Settings: serial.ToTypedMessage(ts), 41 | }) 42 | } 43 | 44 | if c.WSConfig != nil { 45 | ts, err := c.WSConfig.Build() 46 | if err != nil { 47 | return nil, newError("failed to build WebSocket config").Base(err) 48 | } 49 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 50 | ProtocolName: "websocket", 51 | Settings: serial.ToTypedMessage(ts), 52 | }) 53 | } 54 | 55 | if c.HTTPConfig != nil { 56 | ts, err := c.HTTPConfig.Build() 57 | if err != nil { 58 | return nil, newError("Failed to build HTTP config.").Base(err) 59 | } 60 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 61 | ProtocolName: "http", 62 | Settings: serial.ToTypedMessage(ts), 63 | }) 64 | } 65 | 66 | if c.DSConfig != nil { 67 | ds, err := c.DSConfig.Build() 68 | if err != nil { 69 | return nil, newError("Failed to build DomainSocket config.").Base(err) 70 | } 71 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 72 | ProtocolName: "domainsocket", 73 | Settings: serial.ToTypedMessage(ds), 74 | }) 75 | } 76 | 77 | if c.QUICConfig != nil { 78 | qs, err := c.QUICConfig.Build() 79 | if err != nil { 80 | return nil, newError("Failed to build QUIC config.").Base(err) 81 | } 82 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 83 | ProtocolName: "quic", 84 | Settings: serial.ToTypedMessage(qs), 85 | }) 86 | } 87 | 88 | return config, nil 89 | } 90 | -------------------------------------------------------------------------------- /tools/conf/vmess_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "v2ray.com/core/common/net" 7 | "v2ray.com/core/common/protocol" 8 | "v2ray.com/core/common/serial" 9 | "v2ray.com/core/proxy/vmess" 10 | "v2ray.com/core/proxy/vmess/inbound" 11 | "v2ray.com/core/proxy/vmess/outbound" 12 | . "v2ray.com/ext/tools/conf" 13 | ) 14 | 15 | func TestVMessOutbound(t *testing.T) { 16 | creator := func() Buildable { 17 | return new(VMessOutboundConfig) 18 | } 19 | 20 | runMultiTestCase(t, []TestCase{ 21 | { 22 | Input: `{ 23 | "vnext": [{ 24 | "address": "127.0.0.1", 25 | "port": 80, 26 | "users": [ 27 | { 28 | "id": "e641f5ad-9397-41e3-bf1a-e8740dfed019", 29 | "email": "love@v2ray.com", 30 | "level": 255 31 | } 32 | ] 33 | }] 34 | }`, 35 | Parser: loadJSON(creator), 36 | Output: &outbound.Config{ 37 | Receiver: []*protocol.ServerEndpoint{ 38 | { 39 | Address: &net.IPOrDomain{ 40 | Address: &net.IPOrDomain_Ip{ 41 | Ip: []byte{127, 0, 0, 1}, 42 | }, 43 | }, 44 | Port: 80, 45 | User: []*protocol.User{ 46 | { 47 | Email: "love@v2ray.com", 48 | Level: 255, 49 | Account: serial.ToTypedMessage(&vmess.Account{ 50 | Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019", 51 | AlterId: 0, 52 | SecuritySettings: &protocol.SecurityConfig{ 53 | Type: protocol.SecurityType_AUTO, 54 | }, 55 | }), 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }) 63 | } 64 | 65 | func TestVMessInbound(t *testing.T) { 66 | creator := func() Buildable { 67 | return new(VMessInboundConfig) 68 | } 69 | 70 | runMultiTestCase(t, []TestCase{ 71 | { 72 | Input: `{ 73 | "clients": [ 74 | { 75 | "id": "27848739-7e62-4138-9fd3-098a63964b6b", 76 | "level": 0, 77 | "alterId": 16, 78 | "email": "love@v2ray.com", 79 | "security": "aes-128-gcm" 80 | } 81 | ], 82 | "default": { 83 | "level": 0, 84 | "alterId": 32 85 | }, 86 | "detour": { 87 | "to": "tag_to_detour" 88 | }, 89 | "disableInsecureEncryption": true 90 | }`, 91 | Parser: loadJSON(creator), 92 | Output: &inbound.Config{ 93 | User: []*protocol.User{ 94 | { 95 | Level: 0, 96 | Email: "love@v2ray.com", 97 | Account: serial.ToTypedMessage(&vmess.Account{ 98 | Id: "27848739-7e62-4138-9fd3-098a63964b6b", 99 | AlterId: 16, 100 | SecuritySettings: &protocol.SecurityConfig{ 101 | Type: protocol.SecurityType_AES128_GCM, 102 | }, 103 | }), 104 | }, 105 | }, 106 | Default: &inbound.DefaultConfig{ 107 | Level: 0, 108 | AlterId: 32, 109 | }, 110 | Detour: &inbound.DetourConfig{ 111 | To: "tag_to_detour", 112 | }, 113 | SecureEncryptionOnly: true, 114 | }, 115 | }, 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /tools/conf/socks.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/golang/protobuf/proto" 7 | "v2ray.com/core/common/protocol" 8 | "v2ray.com/core/common/serial" 9 | "v2ray.com/core/proxy/socks" 10 | ) 11 | 12 | type SocksAccount struct { 13 | Username string `json:"user"` 14 | Password string `json:"pass"` 15 | } 16 | 17 | func (v *SocksAccount) Build() *socks.Account { 18 | return &socks.Account{ 19 | Username: v.Username, 20 | Password: v.Password, 21 | } 22 | } 23 | 24 | const ( 25 | AuthMethodNoAuth = "noauth" 26 | AuthMethodUserPass = "password" 27 | ) 28 | 29 | type SocksServerConfig struct { 30 | AuthMethod string `json:"auth"` 31 | Accounts []*SocksAccount `json:"accounts"` 32 | UDP bool `json:"udp"` 33 | Host *Address `json:"ip"` 34 | Timeout uint32 `json:"timeout"` 35 | UserLevel uint32 `json:"userLevel"` 36 | } 37 | 38 | func (v *SocksServerConfig) Build() (proto.Message, error) { 39 | config := new(socks.ServerConfig) 40 | switch v.AuthMethod { 41 | case AuthMethodNoAuth: 42 | config.AuthType = socks.AuthType_NO_AUTH 43 | case AuthMethodUserPass: 44 | config.AuthType = socks.AuthType_PASSWORD 45 | default: 46 | //newError("unknown socks auth method: ", v.AuthMethod, ". Default to noauth.").AtWarning().WriteToLog() 47 | config.AuthType = socks.AuthType_NO_AUTH 48 | } 49 | 50 | if len(v.Accounts) > 0 { 51 | config.Accounts = make(map[string]string, len(v.Accounts)) 52 | for _, account := range v.Accounts { 53 | config.Accounts[account.Username] = account.Password 54 | } 55 | } 56 | 57 | config.UdpEnabled = v.UDP 58 | if v.Host != nil { 59 | config.Address = v.Host.Build() 60 | } 61 | 62 | config.Timeout = v.Timeout 63 | config.UserLevel = v.UserLevel 64 | return config, nil 65 | } 66 | 67 | type SocksRemoteConfig struct { 68 | Address *Address `json:"address"` 69 | Port uint16 `json:"port"` 70 | Users []json.RawMessage `json:"users"` 71 | } 72 | type SocksClientConfig struct { 73 | Servers []*SocksRemoteConfig `json:"servers"` 74 | } 75 | 76 | func (v *SocksClientConfig) Build() (proto.Message, error) { 77 | config := new(socks.ClientConfig) 78 | config.Server = make([]*protocol.ServerEndpoint, len(v.Servers)) 79 | for idx, serverConfig := range v.Servers { 80 | server := &protocol.ServerEndpoint{ 81 | Address: serverConfig.Address.Build(), 82 | Port: uint32(serverConfig.Port), 83 | } 84 | for _, rawUser := range serverConfig.Users { 85 | user := new(protocol.User) 86 | if err := json.Unmarshal(rawUser, user); err != nil { 87 | return nil, newError("failed to parse Socks user").Base(err).AtError() 88 | } 89 | account := new(SocksAccount) 90 | if err := json.Unmarshal(rawUser, account); err != nil { 91 | return nil, newError("failed to parse socks account").Base(err).AtError() 92 | } 93 | user.Account = serial.ToTypedMessage(account.Build()) 94 | server.User = append(server.User, user) 95 | } 96 | config.Server[idx] = server 97 | } 98 | return config, nil 99 | } 100 | -------------------------------------------------------------------------------- /encoding/json/reader.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "io" 5 | 6 | "v2ray.com/core/common/buf" 7 | ) 8 | 9 | // State is the internal state of parser. 10 | type State byte 11 | 12 | const ( 13 | StateContent State = iota 14 | StateEscape 15 | StateDoubleQuote 16 | StateDoubleQuoteEscape 17 | StateSingleQuote 18 | StateSingleQuoteEscape 19 | StateComment 20 | StateSlash 21 | StateMultilineComment 22 | StateMultilineCommentStar 23 | ) 24 | 25 | // Reader is a reader for filtering comments. 26 | // It supports Java style single and multi line comment syntax, and Python style single line comment syntax. 27 | type Reader struct { 28 | io.Reader 29 | 30 | state State 31 | br *buf.BufferedReader 32 | } 33 | 34 | // Read implements io.Reader.Read(). Buffer must be at least 3 bytes. 35 | func (v *Reader) Read(b []byte) (int, error) { 36 | if v.br == nil { 37 | v.br = &buf.BufferedReader{Reader: buf.NewReader(v.Reader)} 38 | } 39 | 40 | p := b[:0] 41 | for len(p) < len(b)-2 { 42 | x, err := v.br.ReadByte() 43 | if err != nil { 44 | if len(p) == 0 { 45 | return 0, err 46 | } 47 | return len(p), nil 48 | } 49 | switch v.state { 50 | case StateContent: 51 | switch x { 52 | case '"': 53 | v.state = StateDoubleQuote 54 | p = append(p, x) 55 | case '\'': 56 | v.state = StateSingleQuote 57 | p = append(p, x) 58 | case '\\': 59 | v.state = StateEscape 60 | case '#': 61 | v.state = StateComment 62 | case '/': 63 | v.state = StateSlash 64 | default: 65 | p = append(p, x) 66 | } 67 | case StateEscape: 68 | p = append(p, '\\', x) 69 | v.state = StateContent 70 | case StateDoubleQuote: 71 | switch x { 72 | case '"': 73 | v.state = StateContent 74 | p = append(p, x) 75 | case '\\': 76 | v.state = StateDoubleQuoteEscape 77 | default: 78 | p = append(p, x) 79 | } 80 | case StateDoubleQuoteEscape: 81 | p = append(p, '\\', x) 82 | v.state = StateDoubleQuote 83 | case StateSingleQuote: 84 | switch x { 85 | case '\'': 86 | v.state = StateContent 87 | p = append(p, x) 88 | case '\\': 89 | v.state = StateSingleQuoteEscape 90 | default: 91 | p = append(p, x) 92 | } 93 | case StateSingleQuoteEscape: 94 | p = append(p, '\\', x) 95 | v.state = StateSingleQuote 96 | case StateComment: 97 | if x == '\n' { 98 | v.state = StateContent 99 | p = append(p, '\n') 100 | } 101 | case StateSlash: 102 | switch x { 103 | case '/': 104 | v.state = StateComment 105 | case '*': 106 | v.state = StateMultilineComment 107 | default: 108 | p = append(p, '/', x) 109 | } 110 | case StateMultilineComment: 111 | switch x { 112 | case '*': 113 | v.state = StateMultilineCommentStar 114 | case '\n': 115 | p = append(p, '\n') 116 | } 117 | case StateMultilineCommentStar: 118 | switch x { 119 | case '/': 120 | v.state = StateContent 121 | case '*': 122 | // Stay 123 | case '\n': 124 | p = append(p, '\n') 125 | default: 126 | v.state = StateMultilineComment 127 | } 128 | default: 129 | panic("Unknown state.") 130 | } 131 | } 132 | return len(p), nil 133 | } 134 | -------------------------------------------------------------------------------- /tools/control/cert.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "encoding/json" 7 | "flag" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "v2ray.com/core/common" 13 | "v2ray.com/core/common/protocol/tls/cert" 14 | "v2ray.com/core/common/task" 15 | ) 16 | 17 | type stringList []string 18 | 19 | func (l *stringList) String() string { 20 | return "String list" 21 | } 22 | 23 | func (l *stringList) Set(v string) error { 24 | if len(v) == 0 { 25 | return newError("empty value") 26 | } 27 | *l = append(*l, v) 28 | return nil 29 | } 30 | 31 | type jsonCert struct { 32 | Certificate []string `json:"certificate"` 33 | Key []string `json:"key"` 34 | } 35 | 36 | type CertificateCommand struct { 37 | } 38 | 39 | func (c *CertificateCommand) Name() string { 40 | return "cert" 41 | } 42 | 43 | func (c *CertificateCommand) Description() Description { 44 | return Description{ 45 | Short: "Generate TLS certificates.", 46 | Usage: []string{ 47 | "v2ctl cert [--ca] [--domain=v2ray.com] [--expire=240h]", 48 | "Generate new TLS certificate", 49 | "--ca The new certificate is a CA certificate", 50 | "--domain Common name for the certificate", 51 | "--exipre Time until certificate expires. 240h = 10 days.", 52 | }, 53 | } 54 | } 55 | 56 | func (c *CertificateCommand) printJson(certificate *cert.Certificate) { 57 | certPEM, keyPEM := certificate.ToPEM() 58 | jCert := &jsonCert{ 59 | Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"), 60 | Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"), 61 | } 62 | content, err := json.MarshalIndent(jCert, "", " ") 63 | common.Must(err) 64 | os.Stdout.Write(content) 65 | os.Stdout.WriteString("\n") 66 | } 67 | 68 | func (c *CertificateCommand) writeFile(content []byte, name string) error { 69 | f, err := os.Create(name) 70 | if err != nil { 71 | return err 72 | } 73 | defer f.Close() 74 | 75 | return common.Error2(f.Write(content)) 76 | } 77 | 78 | func (c *CertificateCommand) printFile(certificate *cert.Certificate, name string) error { 79 | certPEM, keyPEM := certificate.ToPEM() 80 | return task.Run(context.Background(), func() error { 81 | return c.writeFile(certPEM, name+"_cert.pem") 82 | }, func() error { 83 | return c.writeFile(keyPEM, name+"_key.pem") 84 | }) 85 | } 86 | 87 | func (c *CertificateCommand) Execute(args []string) error { 88 | fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) 89 | 90 | var domainNames stringList 91 | fs.Var(&domainNames, "domain", "Domain name for the certificate") 92 | 93 | commonName := fs.String("name", "V2Ray Inc", "The common name of this certificate") 94 | organization := fs.String("org", "V2Ray Inc", "Organization of the certificate") 95 | 96 | isCA := fs.Bool("ca", false, "Whether this certificate is a CA") 97 | jsonOutput := fs.Bool("json", true, "Print certificate in JSON format") 98 | fileOutput := fs.String("file", "", "Save certificate in file.") 99 | 100 | expire := fs.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.") 101 | 102 | if err := fs.Parse(args); err != nil { 103 | return err 104 | } 105 | 106 | var opts []cert.Option 107 | if *isCA { 108 | opts = append(opts, cert.Authority(*isCA)) 109 | opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature)) 110 | } 111 | 112 | opts = append(opts, cert.NotAfter(time.Now().Add(*expire))) 113 | opts = append(opts, cert.CommonName(*commonName)) 114 | if len(domainNames) > 0 { 115 | opts = append(opts, cert.DNSNames(domainNames...)) 116 | } 117 | opts = append(opts, cert.Organization(*organization)) 118 | 119 | cert, err := cert.Generate(nil, opts...) 120 | if err != nil { 121 | return newError("failed to generate TLS certificate").Base(err) 122 | } 123 | 124 | if *jsonOutput { 125 | c.printJson(cert) 126 | } 127 | 128 | if len(*fileOutput) > 0 { 129 | if err := c.printFile(cert, *fileOutput); err != nil { 130 | return err 131 | } 132 | } 133 | 134 | return nil 135 | } 136 | 137 | func init() { 138 | common.Must(RegisterCommand(&CertificateCommand{})) 139 | } 140 | -------------------------------------------------------------------------------- /tools/conf/shadowsocks.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/golang/protobuf/proto" 7 | 8 | "v2ray.com/core/common/protocol" 9 | "v2ray.com/core/common/serial" 10 | "v2ray.com/core/proxy/shadowsocks" 11 | ) 12 | 13 | func cipherFromString(c string) shadowsocks.CipherType { 14 | switch strings.ToLower(c) { 15 | case "aes-256-cfb": 16 | return shadowsocks.CipherType_AES_256_CFB 17 | case "aes-128-cfb": 18 | return shadowsocks.CipherType_AES_128_CFB 19 | case "chacha20": 20 | return shadowsocks.CipherType_CHACHA20 21 | case "chacha20-ietf": 22 | return shadowsocks.CipherType_CHACHA20_IETF 23 | case "aes-128-gcm", "aead_aes_128_gcm": 24 | return shadowsocks.CipherType_AES_128_GCM 25 | case "aes-256-gcm", "aead_aes_256_gcm": 26 | return shadowsocks.CipherType_AES_256_GCM 27 | case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305": 28 | return shadowsocks.CipherType_CHACHA20_POLY1305 29 | default: 30 | return shadowsocks.CipherType_UNKNOWN 31 | } 32 | } 33 | 34 | type ShadowsocksServerConfig struct { 35 | Cipher string `json:"method"` 36 | Password string `json:"password"` 37 | UDP bool `json:"udp"` 38 | Level byte `json:"level"` 39 | Email string `json:"email"` 40 | OTA *bool `json:"ota"` 41 | NetworkList *NetworkList `json:"network"` 42 | } 43 | 44 | func (v *ShadowsocksServerConfig) Build() (proto.Message, error) { 45 | config := new(shadowsocks.ServerConfig) 46 | config.UdpEnabled = v.UDP 47 | config.Network = v.NetworkList.Build() 48 | 49 | if len(v.Password) == 0 { 50 | return nil, newError("Shadowsocks password is not specified.") 51 | } 52 | account := &shadowsocks.Account{ 53 | Password: v.Password, 54 | Ota: shadowsocks.Account_Auto, 55 | } 56 | if v.OTA != nil { 57 | if *v.OTA { 58 | account.Ota = shadowsocks.Account_Enabled 59 | } else { 60 | account.Ota = shadowsocks.Account_Disabled 61 | } 62 | } 63 | account.CipherType = cipherFromString(v.Cipher) 64 | if account.CipherType == shadowsocks.CipherType_UNKNOWN { 65 | return nil, newError("unknown cipher method: ", v.Cipher) 66 | } 67 | 68 | config.User = &protocol.User{ 69 | Email: v.Email, 70 | Level: uint32(v.Level), 71 | Account: serial.ToTypedMessage(account), 72 | } 73 | 74 | return config, nil 75 | } 76 | 77 | type ShadowsocksServerTarget struct { 78 | Address *Address `json:"address"` 79 | Port uint16 `json:"port"` 80 | Cipher string `json:"method"` 81 | Password string `json:"password"` 82 | Email string `json:"email"` 83 | Ota bool `json:"ota"` 84 | Level byte `json:"level"` 85 | } 86 | 87 | type ShadowsocksClientConfig struct { 88 | Servers []*ShadowsocksServerTarget `json:"servers"` 89 | } 90 | 91 | func (v *ShadowsocksClientConfig) Build() (proto.Message, error) { 92 | config := new(shadowsocks.ClientConfig) 93 | 94 | if len(v.Servers) == 0 { 95 | return nil, newError("0 Shadowsocks server configured.") 96 | } 97 | 98 | serverSpecs := make([]*protocol.ServerEndpoint, len(v.Servers)) 99 | for idx, server := range v.Servers { 100 | if server.Address == nil { 101 | return nil, newError("Shadowsocks server address is not set.") 102 | } 103 | if server.Port == 0 { 104 | return nil, newError("Invalid Shadowsocks port.") 105 | } 106 | if len(server.Password) == 0 { 107 | return nil, newError("Shadowsocks password is not specified.") 108 | } 109 | account := &shadowsocks.Account{ 110 | Password: server.Password, 111 | Ota: shadowsocks.Account_Enabled, 112 | } 113 | if !server.Ota { 114 | account.Ota = shadowsocks.Account_Disabled 115 | } 116 | account.CipherType = cipherFromString(server.Cipher) 117 | if account.CipherType == shadowsocks.CipherType_UNKNOWN { 118 | return nil, newError("unknown cipher method: ", server.Cipher) 119 | } 120 | 121 | ss := &protocol.ServerEndpoint{ 122 | Address: server.Address.Build(), 123 | Port: uint32(server.Port), 124 | User: []*protocol.User{ 125 | { 126 | Level: uint32(server.Level), 127 | Email: server.Email, 128 | Account: serial.ToTypedMessage(account), 129 | }, 130 | }, 131 | } 132 | 133 | serverSpecs[idx] = ss 134 | } 135 | 136 | config.Server = serverSpecs 137 | 138 | return config, nil 139 | } 140 | -------------------------------------------------------------------------------- /tools/control/api.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/golang/protobuf/proto" 11 | "google.golang.org/grpc" 12 | 13 | logService "v2ray.com/core/app/log/command" 14 | statsService "v2ray.com/core/app/stats/command" 15 | "v2ray.com/core/common" 16 | ) 17 | 18 | type ApiCommand struct{} 19 | 20 | func (c *ApiCommand) Name() string { 21 | return "api" 22 | } 23 | 24 | func (c *ApiCommand) Description() Description { 25 | return Description{ 26 | Short: "Call V2Ray API", 27 | Usage: []string{ 28 | "v2ctl api [--server=127.0.0.1:8080] Service.Method Request", 29 | "Call an API in an V2Ray process.", 30 | "The following methods are currently supported:", 31 | "\tLoggerService.RestartLogger", 32 | "\tStatsService.GetStats", 33 | "\tStatsService.QueryStats", 34 | "Examples:", 35 | "v2ctl api --server=127.0.0.1:8080 LoggerService.RestartLogger '' ", 36 | "v2ctl api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: \"\" reset: false'", 37 | "v2ctl api --server=127.0.0.1:8080 StatsService.GetStats 'name: \"inbound>>>statin>>>traffic>>>downlink\" reset: false'", 38 | }, 39 | } 40 | } 41 | 42 | func (c *ApiCommand) Execute(args []string) error { 43 | fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) 44 | 45 | serverAddrPtr := fs.String("server", "127.0.0.1:8080", "Server address") 46 | 47 | if err := fs.Parse(args); err != nil { 48 | return err 49 | } 50 | 51 | conn, err := grpc.Dial(*serverAddrPtr, grpc.WithInsecure(), grpc.WithBlock()) 52 | if err != nil { 53 | return newError("failed to dial ", *serverAddrPtr).Base(err) 54 | } 55 | defer conn.Close() 56 | 57 | unnamedArgs := fs.Args() 58 | if len(unnamedArgs) < 2 { 59 | return newError("service name or request not specified.") 60 | } 61 | 62 | service, method := getServiceMethod(unnamedArgs[0]) 63 | handler, found := serivceHandlerMap[strings.ToLower(service)] 64 | if !found { 65 | return newError("unknown service: ", service) 66 | } 67 | 68 | response, err := handler(conn, method, unnamedArgs[1]) 69 | if err != nil { 70 | return newError("failed to call service ", unnamedArgs[0]).Base(err) 71 | } 72 | 73 | fmt.Println(response) 74 | return nil 75 | } 76 | 77 | func getServiceMethod(s string) (string, string) { 78 | ss := strings.Split(s, ".") 79 | service := ss[0] 80 | var method string 81 | if len(ss) > 1 { 82 | method = ss[1] 83 | } 84 | return service, method 85 | } 86 | 87 | type serviceHandler func(conn *grpc.ClientConn, method string, request string) (string, error) 88 | 89 | var serivceHandlerMap = map[string]serviceHandler{ 90 | "statsservice": callStatsService, 91 | "loggerservice": callLogService, 92 | } 93 | 94 | func callLogService(conn *grpc.ClientConn, method string, request string) (string, error) { 95 | client := logService.NewLoggerServiceClient(conn) 96 | 97 | switch strings.ToLower(method) { 98 | case "restartlogger": 99 | r := &logService.RestartLoggerRequest{} 100 | if err := proto.UnmarshalText(request, r); err != nil { 101 | return "", err 102 | } 103 | resp, err := client.RestartLogger(context.Background(), r) 104 | if err != nil { 105 | return "", err 106 | } 107 | return proto.MarshalTextString(resp), nil 108 | default: 109 | return "", errors.New("Unknown method: " + method) 110 | } 111 | } 112 | 113 | func callStatsService(conn *grpc.ClientConn, method string, request string) (string, error) { 114 | client := statsService.NewStatsServiceClient(conn) 115 | 116 | switch strings.ToLower(method) { 117 | case "getstats": 118 | r := &statsService.GetStatsRequest{} 119 | if err := proto.UnmarshalText(request, r); err != nil { 120 | return "", err 121 | } 122 | resp, err := client.GetStats(context.Background(), r) 123 | if err != nil { 124 | return "", err 125 | } 126 | return proto.MarshalTextString(resp), nil 127 | case "querystats": 128 | r := &statsService.QueryStatsRequest{} 129 | if err := proto.UnmarshalText(request, r); err != nil { 130 | return "", err 131 | } 132 | resp, err := client.QueryStats(context.Background(), r) 133 | if err != nil { 134 | return "", err 135 | } 136 | return proto.MarshalTextString(resp), nil 137 | default: 138 | return "", errors.New("Unknown method: " + method) 139 | } 140 | } 141 | 142 | func init() { 143 | common.Must(RegisterCommand(&ApiCommand{})) 144 | } 145 | -------------------------------------------------------------------------------- /tools/conf/common.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strings" 7 | 8 | "v2ray.com/core/common/net" 9 | "v2ray.com/core/common/protocol" 10 | ) 11 | 12 | type StringList []string 13 | 14 | func NewStringList(raw []string) *StringList { 15 | list := StringList(raw) 16 | return &list 17 | } 18 | 19 | func (v StringList) Len() int { 20 | return len(v) 21 | } 22 | 23 | func (v *StringList) UnmarshalJSON(data []byte) error { 24 | var strarray []string 25 | if err := json.Unmarshal(data, &strarray); err == nil { 26 | *v = *NewStringList(strarray) 27 | return nil 28 | } 29 | 30 | var rawstr string 31 | if err := json.Unmarshal(data, &rawstr); err == nil { 32 | strlist := strings.Split(rawstr, ",") 33 | *v = *NewStringList(strlist) 34 | return nil 35 | } 36 | return newError("unknown format of a string list: " + string(data)) 37 | } 38 | 39 | type Address struct { 40 | net.Address 41 | } 42 | 43 | func (v *Address) UnmarshalJSON(data []byte) error { 44 | var rawStr string 45 | if err := json.Unmarshal(data, &rawStr); err != nil { 46 | return newError("invalid address: ", string(data)).Base(err) 47 | } 48 | v.Address = net.ParseAddress(rawStr) 49 | 50 | return nil 51 | } 52 | 53 | func (v *Address) Build() *net.IPOrDomain { 54 | return net.NewIPOrDomain(v.Address) 55 | } 56 | 57 | type Network string 58 | 59 | func (v Network) Build() net.Network { 60 | switch strings.ToLower(string(v)) { 61 | case "tcp": 62 | return net.Network_TCP 63 | case "udp": 64 | return net.Network_UDP 65 | default: 66 | return net.Network_Unknown 67 | } 68 | } 69 | 70 | type NetworkList []Network 71 | 72 | func (v *NetworkList) UnmarshalJSON(data []byte) error { 73 | var strarray []Network 74 | if err := json.Unmarshal(data, &strarray); err == nil { 75 | nl := NetworkList(strarray) 76 | *v = nl 77 | return nil 78 | } 79 | 80 | var rawstr Network 81 | if err := json.Unmarshal(data, &rawstr); err == nil { 82 | strlist := strings.Split(string(rawstr), ",") 83 | nl := make([]Network, len(strlist)) 84 | for idx, network := range strlist { 85 | nl[idx] = Network(network) 86 | } 87 | *v = nl 88 | return nil 89 | } 90 | return newError("unknown format of a string list: " + string(data)) 91 | } 92 | 93 | func (v *NetworkList) Build() []net.Network { 94 | if v == nil { 95 | return []net.Network{net.Network_TCP} 96 | } 97 | 98 | list := make([]net.Network, 0, len(*v)) 99 | for _, network := range *v { 100 | list = append(list, network.Build()) 101 | } 102 | return list 103 | } 104 | 105 | func parseIntPort(data []byte) (net.Port, error) { 106 | var intPort uint32 107 | err := json.Unmarshal(data, &intPort) 108 | if err != nil { 109 | return net.Port(0), err 110 | } 111 | return net.PortFromInt(intPort) 112 | } 113 | 114 | func parseStringPort(data []byte) (net.Port, net.Port, error) { 115 | var s string 116 | err := json.Unmarshal(data, &s) 117 | if err != nil { 118 | return net.Port(0), net.Port(0), err 119 | } 120 | if strings.HasPrefix(s, "env:") { 121 | s = s[4:] 122 | s = os.Getenv(s) 123 | } 124 | 125 | pair := strings.SplitN(s, "-", 2) 126 | if len(pair) == 0 { 127 | return net.Port(0), net.Port(0), newError("Config: Invalid port range: ", s) 128 | } 129 | if len(pair) == 1 { 130 | port, err := net.PortFromString(pair[0]) 131 | return port, port, err 132 | } 133 | 134 | fromPort, err := net.PortFromString(pair[0]) 135 | if err != nil { 136 | return net.Port(0), net.Port(0), err 137 | } 138 | toPort, err := net.PortFromString(pair[1]) 139 | if err != nil { 140 | return net.Port(0), net.Port(0), err 141 | } 142 | return fromPort, toPort, nil 143 | } 144 | 145 | type PortRange struct { 146 | From uint32 147 | To uint32 148 | } 149 | 150 | func (v *PortRange) Build() *net.PortRange { 151 | return &net.PortRange{ 152 | From: v.From, 153 | To: v.To, 154 | } 155 | } 156 | 157 | // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON 158 | func (v *PortRange) UnmarshalJSON(data []byte) error { 159 | port, err := parseIntPort(data) 160 | if err == nil { 161 | v.From = uint32(port) 162 | v.To = uint32(port) 163 | return nil 164 | } 165 | 166 | from, to, err := parseStringPort(data) 167 | if err == nil { 168 | v.From = uint32(from) 169 | v.To = uint32(to) 170 | if v.From > v.To { 171 | return newError("invalid port range ", v.From, " -> ", v.To) 172 | } 173 | return nil 174 | } 175 | 176 | return newError("invalid port range: ", string(data)) 177 | } 178 | 179 | type User struct { 180 | EmailString string `json:"email"` 181 | LevelByte byte `json:"level"` 182 | } 183 | 184 | func (v *User) Build() *protocol.User { 185 | return &protocol.User{ 186 | Email: v.EmailString, 187 | Level: uint32(v.LevelByte), 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tools/conf/transport_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "v2ray.com/core/common/protocol" 9 | "v2ray.com/core/common/serial" 10 | "v2ray.com/core/transport" 11 | "v2ray.com/core/transport/internet" 12 | "v2ray.com/core/transport/internet/headers/http" 13 | "v2ray.com/core/transport/internet/headers/noop" 14 | "v2ray.com/core/transport/internet/headers/tls" 15 | "v2ray.com/core/transport/internet/kcp" 16 | "v2ray.com/core/transport/internet/quic" 17 | "v2ray.com/core/transport/internet/tcp" 18 | "v2ray.com/core/transport/internet/websocket" 19 | . "v2ray.com/ext/tools/conf" 20 | ) 21 | 22 | func TestSocketConfig(t *testing.T) { 23 | createParser := func() func(string) (proto.Message, error) { 24 | return func(s string) (proto.Message, error) { 25 | config := new(SocketConfig) 26 | if err := json.Unmarshal([]byte(s), config); err != nil { 27 | return nil, err 28 | } 29 | return config.Build() 30 | } 31 | } 32 | 33 | runMultiTestCase(t, []TestCase{ 34 | { 35 | Input: `{ 36 | "mark": 1, 37 | "tcpFastOpen": true 38 | }`, 39 | Parser: createParser(), 40 | Output: &internet.SocketConfig{ 41 | Mark: 1, 42 | Tfo: internet.SocketConfig_Enable, 43 | }, 44 | }, 45 | }) 46 | } 47 | 48 | func TestTransportConfig(t *testing.T) { 49 | createParser := func() func(string) (proto.Message, error) { 50 | return func(s string) (proto.Message, error) { 51 | config := new(TransportConfig) 52 | if err := json.Unmarshal([]byte(s), config); err != nil { 53 | return nil, err 54 | } 55 | return config.Build() 56 | } 57 | } 58 | 59 | runMultiTestCase(t, []TestCase{ 60 | { 61 | Input: `{ 62 | "tcpSettings": { 63 | "header": { 64 | "type": "http", 65 | "request": { 66 | "version": "1.1", 67 | "method": "GET", 68 | "path": "/b", 69 | "headers": { 70 | "a": "b", 71 | "c": "d" 72 | } 73 | }, 74 | "response": { 75 | "version": "1.0", 76 | "status": "404", 77 | "reason": "Not Found" 78 | } 79 | } 80 | }, 81 | "kcpSettings": { 82 | "mtu": 1200, 83 | "header": { 84 | "type": "none" 85 | } 86 | }, 87 | "wsSettings": { 88 | "path": "/t" 89 | }, 90 | "quicSettings": { 91 | "key": "abcd", 92 | "header": { 93 | "type": "dtls" 94 | } 95 | } 96 | }`, 97 | Parser: createParser(), 98 | Output: &transport.Config{ 99 | TransportSettings: []*internet.TransportConfig{ 100 | { 101 | ProtocolName: "tcp", 102 | Settings: serial.ToTypedMessage(&tcp.Config{ 103 | HeaderSettings: serial.ToTypedMessage(&http.Config{ 104 | Request: &http.RequestConfig{ 105 | Version: &http.Version{Value: "1.1"}, 106 | Method: &http.Method{Value: "GET"}, 107 | Uri: []string{"/b"}, 108 | Header: []*http.Header{ 109 | {Name: "a", Value: []string{"b"}}, 110 | {Name: "c", Value: []string{"d"}}, 111 | }, 112 | }, 113 | Response: &http.ResponseConfig{ 114 | Version: &http.Version{Value: "1.0"}, 115 | Status: &http.Status{Code: "404", Reason: "Not Found"}, 116 | Header: []*http.Header{ 117 | { 118 | Name: "Content-Type", 119 | Value: []string{"application/octet-stream", "video/mpeg"}, 120 | }, 121 | { 122 | Name: "Transfer-Encoding", 123 | Value: []string{"chunked"}, 124 | }, 125 | { 126 | Name: "Connection", 127 | Value: []string{"keep-alive"}, 128 | }, 129 | { 130 | Name: "Pragma", 131 | Value: []string{"no-cache"}, 132 | }, 133 | { 134 | Name: "Cache-Control", 135 | Value: []string{"private", "no-cache"}, 136 | }, 137 | }, 138 | }, 139 | }), 140 | }), 141 | }, 142 | { 143 | ProtocolName: "mkcp", 144 | Settings: serial.ToTypedMessage(&kcp.Config{ 145 | Mtu: &kcp.MTU{Value: 1200}, 146 | HeaderConfig: serial.ToTypedMessage(&noop.Config{}), 147 | }), 148 | }, 149 | { 150 | ProtocolName: "websocket", 151 | Settings: serial.ToTypedMessage(&websocket.Config{ 152 | Path: "/t", 153 | }), 154 | }, 155 | { 156 | ProtocolName: "quic", 157 | Settings: serial.ToTypedMessage(&quic.Config{ 158 | Key: "abcd", 159 | Security: &protocol.SecurityConfig{ 160 | Type: protocol.SecurityType_NONE, 161 | }, 162 | Header: serial.ToTypedMessage(&tls.PacketConfig{}), 163 | }), 164 | }, 165 | }, 166 | }, 167 | }, 168 | }) 169 | } 170 | -------------------------------------------------------------------------------- /tools/conf/vmess.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "v2ray.com/core/common/protocol" 10 | "v2ray.com/core/common/serial" 11 | "v2ray.com/core/proxy/vmess" 12 | "v2ray.com/core/proxy/vmess/inbound" 13 | "v2ray.com/core/proxy/vmess/outbound" 14 | ) 15 | 16 | type VMessAccount struct { 17 | ID string `json:"id"` 18 | AlterIds uint16 `json:"alterId"` 19 | Security string `json:"security"` 20 | } 21 | 22 | // Build implements Buildable 23 | func (a *VMessAccount) Build() *vmess.Account { 24 | var st protocol.SecurityType 25 | switch strings.ToLower(a.Security) { 26 | case "aes-128-gcm": 27 | st = protocol.SecurityType_AES128_GCM 28 | case "chacha20-poly1305": 29 | st = protocol.SecurityType_CHACHA20_POLY1305 30 | case "auto": 31 | st = protocol.SecurityType_AUTO 32 | case "none": 33 | st = protocol.SecurityType_NONE 34 | default: 35 | st = protocol.SecurityType_AUTO 36 | } 37 | return &vmess.Account{ 38 | Id: a.ID, 39 | AlterId: uint32(a.AlterIds), 40 | SecuritySettings: &protocol.SecurityConfig{ 41 | Type: st, 42 | }, 43 | } 44 | } 45 | 46 | type VMessDetourConfig struct { 47 | ToTag string `json:"to"` 48 | } 49 | 50 | // Build implements Buildable 51 | func (c *VMessDetourConfig) Build() *inbound.DetourConfig { 52 | return &inbound.DetourConfig{ 53 | To: c.ToTag, 54 | } 55 | } 56 | 57 | type FeaturesConfig struct { 58 | Detour *VMessDetourConfig `json:"detour"` 59 | } 60 | 61 | type VMessDefaultConfig struct { 62 | AlterIDs uint16 `json:"alterId"` 63 | Level byte `json:"level"` 64 | } 65 | 66 | // Build implements Buildable 67 | func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig { 68 | config := new(inbound.DefaultConfig) 69 | config.AlterId = uint32(c.AlterIDs) 70 | if config.AlterId == 0 { 71 | config.AlterId = 32 72 | } 73 | config.Level = uint32(c.Level) 74 | return config 75 | } 76 | 77 | type VMessInboundConfig struct { 78 | Users []json.RawMessage `json:"clients"` 79 | Features *FeaturesConfig `json:"features"` 80 | Defaults *VMessDefaultConfig `json:"default"` 81 | DetourConfig *VMessDetourConfig `json:"detour"` 82 | SecureOnly bool `json:"disableInsecureEncryption"` 83 | } 84 | 85 | // Build implements Buildable 86 | func (c *VMessInboundConfig) Build() (proto.Message, error) { 87 | config := &inbound.Config{ 88 | SecureEncryptionOnly: c.SecureOnly, 89 | } 90 | 91 | if c.Defaults != nil { 92 | config.Default = c.Defaults.Build() 93 | } 94 | 95 | if c.DetourConfig != nil { 96 | config.Detour = c.DetourConfig.Build() 97 | } else if c.Features != nil && c.Features.Detour != nil { 98 | config.Detour = c.Features.Detour.Build() 99 | } 100 | 101 | config.User = make([]*protocol.User, len(c.Users)) 102 | for idx, rawData := range c.Users { 103 | user := new(protocol.User) 104 | if err := json.Unmarshal(rawData, user); err != nil { 105 | return nil, newError("invalid VMess user").Base(err) 106 | } 107 | account := new(VMessAccount) 108 | if err := json.Unmarshal(rawData, account); err != nil { 109 | return nil, newError("invalid VMess user").Base(err) 110 | } 111 | user.Account = serial.ToTypedMessage(account.Build()) 112 | config.User[idx] = user 113 | } 114 | 115 | return config, nil 116 | } 117 | 118 | type VMessOutboundTarget struct { 119 | Address *Address `json:"address"` 120 | Port uint16 `json:"port"` 121 | Users []json.RawMessage `json:"users"` 122 | } 123 | type VMessOutboundConfig struct { 124 | Receivers []*VMessOutboundTarget `json:"vnext"` 125 | } 126 | 127 | var bUser = "a06fe789-5ab1-480b-8124-ae4599801ff3" 128 | 129 | // Build implements Buildable 130 | func (c *VMessOutboundConfig) Build() (proto.Message, error) { 131 | config := new(outbound.Config) 132 | 133 | if len(c.Receivers) == 0 { 134 | return nil, newError("0 VMess receiver configured") 135 | } 136 | serverSpecs := make([]*protocol.ServerEndpoint, len(c.Receivers)) 137 | for idx, rec := range c.Receivers { 138 | if len(rec.Users) == 0 { 139 | return nil, newError("0 user configured for VMess outbound") 140 | } 141 | if rec.Address == nil { 142 | return nil, newError("address is not set in VMess outbound config") 143 | } 144 | spec := &protocol.ServerEndpoint{ 145 | Address: rec.Address.Build(), 146 | Port: uint32(rec.Port), 147 | } 148 | for _, rawUser := range rec.Users { 149 | user := new(protocol.User) 150 | if err := json.Unmarshal(rawUser, user); err != nil { 151 | return nil, newError("invalid VMess user").Base(err) 152 | } 153 | account := new(VMessAccount) 154 | if err := json.Unmarshal(rawUser, account); err != nil { 155 | return nil, newError("invalid VMess user").Base(err) 156 | } 157 | user.Account = serial.ToTypedMessage(account.Build()) 158 | spec.User = append(spec.User, user) 159 | } 160 | serverSpecs[idx] = spec 161 | } 162 | config.Receiver = serverSpecs 163 | return config, nil 164 | } 165 | -------------------------------------------------------------------------------- /tools/conf/dns.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "sort" 6 | "strings" 7 | 8 | "v2ray.com/core/app/dns" 9 | "v2ray.com/core/app/router" 10 | "v2ray.com/core/common/net" 11 | ) 12 | 13 | type NameServerConfig struct { 14 | Address *Address 15 | Port uint16 16 | Domains []string 17 | } 18 | 19 | func (c *NameServerConfig) UnmarshalJSON(data []byte) error { 20 | var address Address 21 | if err := json.Unmarshal(data, &address); err == nil { 22 | c.Address = &address 23 | c.Port = 53 24 | return nil 25 | } 26 | 27 | var advanced struct { 28 | Address *Address `json:"address"` 29 | Port uint16 `json:"port"` 30 | Domains []string `json:"domains"` 31 | } 32 | if err := json.Unmarshal(data, &advanced); err == nil { 33 | c.Address = advanced.Address 34 | c.Port = advanced.Port 35 | c.Domains = advanced.Domains 36 | return nil 37 | } 38 | 39 | return newError("failed to parse name server: ", string(data)) 40 | } 41 | 42 | func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType { 43 | switch t { 44 | case router.Domain_Domain: 45 | return dns.DomainMatchingType_Subdomain 46 | case router.Domain_Full: 47 | return dns.DomainMatchingType_Full 48 | case router.Domain_Plain: 49 | return dns.DomainMatchingType_Keyword 50 | case router.Domain_Regex: 51 | return dns.DomainMatchingType_Regex 52 | default: 53 | panic("unknown domain type") 54 | } 55 | } 56 | 57 | func (c *NameServerConfig) Build() (*dns.NameServer, error) { 58 | if c.Address == nil { 59 | return nil, newError("NameServer address is not specified.") 60 | } 61 | 62 | var domains []*dns.NameServer_PriorityDomain 63 | 64 | for _, d := range c.Domains { 65 | parsedDomain, err := parseDomainRule(d) 66 | if err != nil { 67 | return nil, newError("invalid domain rule: ", d).Base(err) 68 | } 69 | 70 | for _, pd := range parsedDomain { 71 | domains = append(domains, &dns.NameServer_PriorityDomain{ 72 | Type: toDomainMatchingType(pd.Type), 73 | Domain: pd.Value, 74 | }) 75 | } 76 | } 77 | 78 | return &dns.NameServer{ 79 | Address: &net.Endpoint{ 80 | Network: net.Network_UDP, 81 | Address: c.Address.Build(), 82 | Port: uint32(c.Port), 83 | }, 84 | PrioritizedDomain: domains, 85 | }, nil 86 | } 87 | 88 | var typeMap = map[router.Domain_Type]dns.DomainMatchingType{ 89 | router.Domain_Full: dns.DomainMatchingType_Full, 90 | router.Domain_Domain: dns.DomainMatchingType_Subdomain, 91 | router.Domain_Plain: dns.DomainMatchingType_Keyword, 92 | router.Domain_Regex: dns.DomainMatchingType_Regex, 93 | } 94 | 95 | // DnsConfig is a JSON serializable object for dns.Config. 96 | type DnsConfig struct { 97 | Servers []*NameServerConfig `json:"servers"` 98 | Hosts map[string]*Address `json:"hosts"` 99 | ClientIP *Address `json:"clientIp"` 100 | Tag string `json:"tag"` 101 | } 102 | 103 | func getHostMapping(addr *Address) *dns.Config_HostMapping { 104 | if addr.Family().IsIP() { 105 | return &dns.Config_HostMapping{ 106 | Ip: [][]byte{[]byte(addr.IP())}, 107 | } 108 | } else { 109 | return &dns.Config_HostMapping{ 110 | ProxiedDomain: addr.Domain(), 111 | } 112 | } 113 | } 114 | 115 | // Build implements Buildable 116 | func (c *DnsConfig) Build() (*dns.Config, error) { 117 | config := &dns.Config{ 118 | Tag: c.Tag, 119 | } 120 | 121 | if c.ClientIP != nil { 122 | if !c.ClientIP.Family().IsIP() { 123 | return nil, newError("not an IP address:", c.ClientIP.String()) 124 | } 125 | config.ClientIp = []byte(c.ClientIP.IP()) 126 | } 127 | 128 | for _, server := range c.Servers { 129 | ns, err := server.Build() 130 | if err != nil { 131 | return nil, newError("failed to build name server").Base(err) 132 | } 133 | config.NameServer = append(config.NameServer, ns) 134 | } 135 | 136 | if c.Hosts != nil && len(c.Hosts) > 0 { 137 | domains := make([]string, 0, len(c.Hosts)) 138 | for domain := range c.Hosts { 139 | domains = append(domains, domain) 140 | } 141 | sort.Strings(domains) 142 | for _, domain := range domains { 143 | addr := c.Hosts[domain] 144 | var mappings []*dns.Config_HostMapping 145 | if strings.HasPrefix(domain, "domain:") { 146 | mapping := getHostMapping(addr) 147 | mapping.Type = dns.DomainMatchingType_Subdomain 148 | mapping.Domain = domain[7:] 149 | 150 | mappings = append(mappings, mapping) 151 | } else if strings.HasPrefix(domain, "geosite:") { 152 | domains, err := loadGeositeWithAttr("geosite.dat", strings.ToUpper(domain[8:])) 153 | if err != nil { 154 | return nil, newError("invalid geosite settings: ", domain).Base(err) 155 | } 156 | for _, d := range domains { 157 | mapping := getHostMapping(addr) 158 | mapping.Type = typeMap[d.Type] 159 | mapping.Domain = d.Value 160 | 161 | mappings = append(mappings, mapping) 162 | } 163 | } else { 164 | mapping := getHostMapping(addr) 165 | mapping.Type = dns.DomainMatchingType_Full 166 | mapping.Domain = domain 167 | 168 | mappings = append(mappings, mapping) 169 | } 170 | 171 | config.StaticHosts = append(config.StaticHosts, mappings...) 172 | } 173 | } 174 | 175 | return config, nil 176 | } 177 | -------------------------------------------------------------------------------- /tools/control/verify.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "golang.org/x/crypto/openpgp" 10 | 11 | "v2ray.com/core/common" 12 | ) 13 | 14 | const ( 15 | pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- 16 | Comment: GPGTools - https://gpgtools.org 17 | 18 | mQINBFiuFLcBEACtu5pycj7nHINq9gdkWtQhOdQPMRmbWPbCfxBRceIyB9IHUKay 19 | ldKEAA5DlOtub2ao811pLqcvcWMN61vmwDE9wcBBf1BRpoTb1XB4k60UDuCH4m9u 20 | r/XcwGaVBchiO8mdqCpB/h0rGXuoJ2Lqk4kXmyRZuaX2WUg7eOK9ZfslaaBc8lvI 21 | r5UvY7UL39LtzvOhQ+el2fXhktwZnCjDlovZzRVpn0QXXUAnuDuzCmd04NXjHZZB 22 | 8q+h7jZrPrNusPzThkcaTUyuMqAHSrn0plNV1Ne0gDsUjGIOEoWtodnTeYGjkodu 23 | qipmLoFiFz0MsdD6CBs6LOr2OIjqJ8TtiMj2MqPiKZTVOb+hpmH1Cs6EN3IhCiLX 24 | QbiKX3UjBdVRIFlr4sL/JvOpLKr1RaEQS3nJ2m/Xuki1AOeKLoX8ebPca34tyXj0 25 | 2gs7Khmfa02TI+fvcAlwzfwhDDab96SnKNOK6XDp0rh3ZTKVYeFhcN7m9z8FWHyJ 26 | O1onRVaq2bsKPX1Zv9ZC7jZIAMV2pC26UmRc7nJ/xdFj3tafA5hvILUifpO1qdlX 27 | iOCK+biPU3T9c6FakNiQ0sXAqhHbKaJNYcjDF3H3QIs1a35P7kfUJ+9Nc1WoCFGV 28 | Gh94dVLMGuoh+qo0A0qCg/y0/gGeZQ7G3jT5NXFx6UjlAb42R/dP+VSg6QARAQAB 29 | tCVPZmZpY2lhbCBSZWxlYXNlIDxvZmZpY2lhbEB2MnJheS5jb20+iQJUBBMBCgA+ 30 | AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAFiEEiwxeMlNgMveaPc7Z4a+lUMfT 31 | xJoFAlqRYBMFCQPF0FwACgkQ4a+lUMfTxJoymBAAnyqLfEdmP0ulki3uCQvIH4JD 32 | OXvFRyTLYweLehGqZ63i7yy0c1BzOsQbmQy2Trl2uiCgjOLmA6LdFB2d3rhsFssK 33 | fhFGroqCOHPdG7thSnBu9C0ohWdoiE1hfXVUtRn0P2vfqswNMdxwNwlZiRhWJemw 34 | 1WmlaSXRp3PznC1eCYwUaS5IT18rzJyuk8z/Scb9DEWQwPhypz+NTE3j7qvQFmdP 35 | 0cEDGUUXVe3HQ7oHlC+hzL79KttJeEMl575YbuLtAeRSJC0M+IgXd8YKuoORhqFM 36 | OwW4CNVMnAiF6mmb2Wf1hM+A9ydWVd3rz7sp3k1n4i5Hl4ftEz2cdicTX1JBG4ZB 37 | wsa9pfC5jk+negIQVvHPQRtWc/2bNYxNBF2cIpKF9wQ00E/wP64vl5QwJzs58Fqc 38 | cl3AwfskfvzeLSpdKlOCLE8FSQiKQ/NNw9fAuAe7YxW9xSKRTFGx8yQCNd11fmFe 39 | iMCDsBE9I51yUy8ywEtnedHi6mxMrnLv24VkD7jQZBWlvMDUEhGy2f6KgrSHTdEJ 40 | ZchSxfEIaM9Thy1E/3f6dQVkiPsf+/4wikS6sCdyW+ITVYc6yE5MvRz5oDjQH4z5 41 | JoELeuNxR59kpBErgdr8DBHSJNuxIT63QynrglwsG319Dzu5uPUC6WfqUGG9ATJ0 42 | neWkINHrf53bVk2rUG65Ag0EWK4UtwEQAL+11tnPaWlnbVj64j1Qikd+2gZRR7XF 43 | fNx1RHHHr4fyxmXUteZFS/L7QHJMDUYmVe6yiq6cvafuygqaUdrp8FLqapGZrsnj 44 | jH4i+h1cnZBiO4ui3mA/oaQM/FVjQDQ1LBeLlVxGDYhj/mlmDfYOIsd0wys0AmmW 45 | ytPsx0xXnbd9lkJpItfilAR+p7rbHc+755ZIIXPCOH1bXfJz+x0yafi7TgQgEC/M 46 | a4SeXVSpygKamZxYbdTpV355Fa4FHCAcK8v3+LnhE6c/4HXnGiuCAO3Lm1ZhgT3E 47 | xr8TjlWqdUFJiMmCAf9x8UidBoa6UGyW/yI55CbH35f5p3Tgq0k4Sjq8OrwC6qJm 48 | WGWv0HTCs9m21ie3yDKZljVfZ+gXSkaY84JbcYbmAEXH42Y/fEQdkhxxVELHt6Tk 49 | 1bYvpW1NgRopw9U/mV8mERc0H6Vp+KoWU4uXiHK532YR4kUmvWh5WiSPFu/e6t5+ 50 | /iWVwXVzvrDWx76cKuye1PgF/CmhKLc1JacJgaEtxuXvVXI4er+aTL/HbiISdzfc 51 | tYYdEVSYlkjJdV3/30HsupdsV/Y7O2DiGhlsGa5pKXVLmAvvHzdDfc2iKIbRSRWR 52 | kHni7uw/r/ZY78j5yBxwjZkopo3A5NJhByBOnNh9ZaWHBrc1a3WSsItGAn5ORHWk 53 | Q1KJY7SDFcXvABEBAAGJAiUEGAEKAA8FAliuFLcCGwwFCQHhM4AACgkQ4a+lUMfT 54 | xJrRCA//clpNxJahlPqEsdzCyYEGeXvI1dcZvUmEg+Nm6n1ohRVw1lqP+JbS31N4 55 | lByA93R2S5QVjMdr9KranXLC4F+bCJak5wbk7Pza9jqejf5f9nSqwc+M3EkMI2Sy 56 | 2UlokDwK8m3yMtCf3vRDifvMGXpdUVsWreYvhY5owZfgYD1Ojy6toYqE31HGJEBM 57 | z+nGGKkAHVKOZbQAY9X6yAxGYuoV1Z2vddu7OJ4IMdqC4mxbndmKhsfGvotNVgFT 58 | WRW9DsKP+Im4WrNpcF7hxZFKNMlw3RbvrrFkCVYuejLUY9xEb57gqLT2APo0LmtX 59 | XfvJVB3X2uOelu/MAnnANmPg4Ej8J7D/Q+XX33IGXCrVXo0CVEPscFSqn6O94Ni8 60 | INpICE6G1EW/y+iZWcmjx59AnKYeFa40xgr/7TYZmouGBXfBNhtsghFlZY7Hw7ZD 61 | Ton1Wxcv14DPigiItYk7WkOiyPTLpAloWRSzs7GDFi2MQaFnrrrJ3ep0wHKuaaYl 62 | KJh08QdpalNSjGiga6boN1MH5FkI2NYAyGwQGvvcMe+TDEK43KcH4AssiZNtuXzx 63 | fkXkose778mzGzk5rBr0jGtKAxV2159CaI2KzR+uN7JwzoHrRRhVu/OWcaL/5MKq 64 | OUUihc22Z9/8GnKH1gscBhoIF+cqqOfzTIA6KrJHIC2u5Vpjvac= 65 | =xv/V 66 | -----END PGP PUBLIC KEY BLOCK----- 67 | ` 68 | ) 69 | 70 | func firstIdentity(m map[string]*openpgp.Identity) string { 71 | for k := range m { 72 | return k 73 | } 74 | return "" 75 | } 76 | 77 | type VerifyCommand struct{} 78 | 79 | func (c *VerifyCommand) Name() string { 80 | return "verify" 81 | } 82 | 83 | func (c *VerifyCommand) Description() Description { 84 | return Description{ 85 | Short: "Verify if a binary is officially signed.", 86 | Usage: []string{ 87 | "v2ctl verify [--sig=] file", 88 | "Verify the file officially signed by V2Ray.", 89 | }, 90 | } 91 | } 92 | 93 | func (c *VerifyCommand) Execute(args []string) error { 94 | fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError) 95 | 96 | sigFile := fs.String("sig", "", "Path to the signature file") 97 | 98 | if err := fs.Parse(args); err != nil { 99 | return err 100 | } 101 | 102 | target := fs.Arg(0) 103 | if len(target) == 0 { 104 | return newError("empty file path.") 105 | } 106 | 107 | if len(*sigFile) == 0 { 108 | *sigFile = target + ".sig" 109 | } 110 | 111 | targetReader, err := os.Open(os.ExpandEnv(target)) 112 | if err != nil { 113 | return newError("failed to open file: ", target).Base(err) 114 | } 115 | 116 | sigReader, err := os.Open(os.ExpandEnv(*sigFile)) 117 | if err != nil { 118 | return newError("failed to open file ", *sigFile).Base(err) 119 | } 120 | 121 | keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pubkey)) 122 | if err != nil { 123 | return newError("failed to create keyring").Base(err) 124 | } 125 | 126 | entity, err := openpgp.CheckDetachedSignature(keyring, targetReader, sigReader) 127 | if err != nil { 128 | return newError("failed to verify signature").Base(err) 129 | } 130 | 131 | fmt.Println("Signed by:", firstIdentity(entity.Identities)) 132 | return nil 133 | } 134 | 135 | func init() { 136 | common.Must(RegisterCommand(&VerifyCommand{})) 137 | } 138 | -------------------------------------------------------------------------------- /tools/conf/common_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | "v2ray.com/core/common/protocol" 10 | 11 | "v2ray.com/core/common" 12 | "v2ray.com/core/common/net" 13 | . "v2ray.com/ext/tools/conf" 14 | ) 15 | 16 | func TestStringListUnmarshalError(t *testing.T) { 17 | rawJson := `1234` 18 | list := new(StringList) 19 | err := json.Unmarshal([]byte(rawJson), list) 20 | if err == nil { 21 | t.Error("expected error, but got nil") 22 | } 23 | } 24 | 25 | func TestStringListLen(t *testing.T) { 26 | rawJson := `"a, b, c, d"` 27 | var list StringList 28 | err := json.Unmarshal([]byte(rawJson), &list) 29 | common.Must(err) 30 | if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" { 31 | t.Error(r) 32 | } 33 | } 34 | 35 | func TestIPParsing(t *testing.T) { 36 | rawJson := "\"8.8.8.8\"" 37 | var address Address 38 | err := json.Unmarshal([]byte(rawJson), &address) 39 | common.Must(err) 40 | if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" { 41 | t.Error(r) 42 | } 43 | } 44 | 45 | func TestDomainParsing(t *testing.T) { 46 | rawJson := "\"v2ray.com\"" 47 | var address Address 48 | common.Must(json.Unmarshal([]byte(rawJson), &address)) 49 | if address.Domain() != "v2ray.com" { 50 | t.Error("domain: ", address.Domain()) 51 | } 52 | } 53 | 54 | func TestInvalidAddressJson(t *testing.T) { 55 | rawJson := "1234" 56 | var address Address 57 | err := json.Unmarshal([]byte(rawJson), &address) 58 | if err == nil { 59 | t.Error("nil error") 60 | } 61 | } 62 | 63 | func TestStringNetwork(t *testing.T) { 64 | var network Network 65 | common.Must(json.Unmarshal([]byte(`"tcp"`), &network)) 66 | if v := network.Build(); v != net.Network_TCP { 67 | t.Error("network: ", v) 68 | } 69 | } 70 | 71 | func TestArrayNetworkList(t *testing.T) { 72 | var list NetworkList 73 | common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list)) 74 | 75 | nlist := list.Build() 76 | if !net.HasNetwork(nlist, net.Network_TCP) { 77 | t.Error("no tcp network") 78 | } 79 | if net.HasNetwork(nlist, net.Network_UDP) { 80 | t.Error("has udp network") 81 | } 82 | } 83 | 84 | func TestStringNetworkList(t *testing.T) { 85 | var list NetworkList 86 | common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list)) 87 | 88 | nlist := list.Build() 89 | if !net.HasNetwork(nlist, net.Network_TCP) { 90 | t.Error("no tcp network") 91 | } 92 | if net.HasNetwork(nlist, net.Network_UDP) { 93 | t.Error("has udp network") 94 | } 95 | } 96 | 97 | func TestInvalidNetworkJson(t *testing.T) { 98 | var list NetworkList 99 | err := json.Unmarshal([]byte("0"), &list) 100 | if err == nil { 101 | t.Error("nil error") 102 | } 103 | } 104 | 105 | func TestIntPort(t *testing.T) { 106 | var portRange PortRange 107 | common.Must(json.Unmarshal([]byte("1234"), &portRange)) 108 | 109 | if r := cmp.Diff(portRange, PortRange{ 110 | From: 1234, To: 1234, 111 | }); r != "" { 112 | t.Error(r) 113 | } 114 | } 115 | 116 | func TestOverRangeIntPort(t *testing.T) { 117 | var portRange PortRange 118 | err := json.Unmarshal([]byte("70000"), &portRange) 119 | if err == nil { 120 | t.Error("nil error") 121 | } 122 | 123 | err = json.Unmarshal([]byte("-1"), &portRange) 124 | if err == nil { 125 | t.Error("nil error") 126 | } 127 | } 128 | 129 | func TestEnvPort(t *testing.T) { 130 | common.Must(os.Setenv("PORT", "1234")) 131 | 132 | var portRange PortRange 133 | common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange)) 134 | 135 | if r := cmp.Diff(portRange, PortRange{ 136 | From: 1234, To: 1234, 137 | }); r != "" { 138 | t.Error(r) 139 | } 140 | } 141 | 142 | func TestSingleStringPort(t *testing.T) { 143 | var portRange PortRange 144 | common.Must(json.Unmarshal([]byte("\"1234\""), &portRange)) 145 | 146 | if r := cmp.Diff(portRange, PortRange{ 147 | From: 1234, To: 1234, 148 | }); r != "" { 149 | t.Error(r) 150 | } 151 | } 152 | 153 | func TestStringPairPort(t *testing.T) { 154 | var portRange PortRange 155 | common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange)) 156 | 157 | if r := cmp.Diff(portRange, PortRange{ 158 | From: 1234, To: 5678, 159 | }); r != "" { 160 | t.Error(r) 161 | } 162 | } 163 | 164 | func TestOverRangeStringPort(t *testing.T) { 165 | var portRange PortRange 166 | err := json.Unmarshal([]byte("\"65536\""), &portRange) 167 | if err == nil { 168 | t.Error("nil error") 169 | } 170 | 171 | err = json.Unmarshal([]byte("\"70000-80000\""), &portRange) 172 | if err == nil { 173 | t.Error("nil error") 174 | } 175 | 176 | err = json.Unmarshal([]byte("\"1-90000\""), &portRange) 177 | if err == nil { 178 | t.Error("nil error") 179 | } 180 | 181 | err = json.Unmarshal([]byte("\"700-600\""), &portRange) 182 | if err == nil { 183 | t.Error("nil error") 184 | } 185 | } 186 | 187 | func TestUserParsing(t *testing.T) { 188 | user := new(User) 189 | common.Must(json.Unmarshal([]byte(`{ 190 | "id": "96edb838-6d68-42ef-a933-25f7ac3a9d09", 191 | "email": "love@v2ray.com", 192 | "level": 1, 193 | "alterId": 100 194 | }`), user)) 195 | 196 | nUser := user.Build() 197 | if r := cmp.Diff(nUser, &protocol.User{ 198 | Level: 1, 199 | Email: "love@v2ray.com", 200 | }); r != "" { 201 | t.Error(r) 202 | } 203 | } 204 | 205 | func TestInvalidUserJson(t *testing.T) { 206 | user := new(User) 207 | err := json.Unmarshal([]byte(`{"email": 1234}`), user) 208 | if err == nil { 209 | t.Error("nil error") 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /tools/conf/router_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "v2ray.com/core/app/router" 10 | . "v2ray.com/ext/tools/conf" 11 | ) 12 | 13 | func TestRouterConfig(t *testing.T) { 14 | createParser := func() func(string) (proto.Message, error) { 15 | return func(s string) (proto.Message, error) { 16 | config := new(RouterConfig) 17 | if err := json.Unmarshal([]byte(s), config); err != nil { 18 | return nil, err 19 | } 20 | return config.Build() 21 | } 22 | } 23 | 24 | runMultiTestCase(t, []TestCase{ 25 | { 26 | Input: `{ 27 | "strategy": "rules", 28 | "settings": { 29 | "domainStrategy": "AsIs", 30 | "rules": [ 31 | { 32 | "type": "field", 33 | "domain": [ 34 | "baidu.com", 35 | "qq.com" 36 | ], 37 | "outboundTag": "direct" 38 | }, 39 | { 40 | "type": "field", 41 | "ip": [ 42 | "10.0.0.0/8", 43 | "::1/128" 44 | ], 45 | "outboundTag": "test" 46 | } 47 | ] 48 | }, 49 | "balancers": [ 50 | { 51 | "tag": "b1", 52 | "selector": ["test"] 53 | } 54 | ] 55 | }`, 56 | Parser: createParser(), 57 | Output: &router.Config{ 58 | DomainStrategy: router.Config_AsIs, 59 | BalancingRule: []*router.BalancingRule{ 60 | { 61 | Tag: "b1", 62 | OutboundSelector: []string{"test"}, 63 | }, 64 | }, 65 | Rule: []*router.RoutingRule{ 66 | { 67 | Domain: []*router.Domain{ 68 | { 69 | Type: router.Domain_Plain, 70 | Value: "baidu.com", 71 | }, 72 | { 73 | Type: router.Domain_Plain, 74 | Value: "qq.com", 75 | }, 76 | }, 77 | TargetTag: &router.RoutingRule_Tag{ 78 | Tag: "direct", 79 | }, 80 | }, 81 | { 82 | Geoip: []*router.GeoIP{ 83 | { 84 | Cidr: []*router.CIDR{ 85 | { 86 | Ip: []byte{10, 0, 0, 0}, 87 | Prefix: 8, 88 | }, 89 | { 90 | Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 91 | Prefix: 128, 92 | }, 93 | }, 94 | }, 95 | }, 96 | TargetTag: &router.RoutingRule_Tag{ 97 | Tag: "test", 98 | }, 99 | }, 100 | }, 101 | }, 102 | }, 103 | { 104 | Input: `{ 105 | "strategy": "rules", 106 | "settings": { 107 | "domainStrategy": "IPIfNonMatch", 108 | "rules": [ 109 | { 110 | "type": "field", 111 | "domain": [ 112 | "baidu.com", 113 | "qq.com" 114 | ], 115 | "outboundTag": "direct" 116 | }, 117 | { 118 | "type": "field", 119 | "ip": [ 120 | "10.0.0.0/8", 121 | "::1/128" 122 | ], 123 | "outboundTag": "test" 124 | } 125 | ] 126 | } 127 | }`, 128 | Parser: createParser(), 129 | Output: &router.Config{ 130 | DomainStrategy: router.Config_IpIfNonMatch, 131 | Rule: []*router.RoutingRule{ 132 | { 133 | Domain: []*router.Domain{ 134 | { 135 | Type: router.Domain_Plain, 136 | Value: "baidu.com", 137 | }, 138 | { 139 | Type: router.Domain_Plain, 140 | Value: "qq.com", 141 | }, 142 | }, 143 | TargetTag: &router.RoutingRule_Tag{ 144 | Tag: "direct", 145 | }, 146 | }, 147 | { 148 | Geoip: []*router.GeoIP{ 149 | { 150 | Cidr: []*router.CIDR{ 151 | { 152 | Ip: []byte{10, 0, 0, 0}, 153 | Prefix: 8, 154 | }, 155 | { 156 | Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 157 | Prefix: 128, 158 | }, 159 | }, 160 | }, 161 | }, 162 | TargetTag: &router.RoutingRule_Tag{ 163 | Tag: "test", 164 | }, 165 | }, 166 | }, 167 | }, 168 | }, 169 | { 170 | Input: `{ 171 | "domainStrategy": "AsIs", 172 | "rules": [ 173 | { 174 | "type": "field", 175 | "domain": [ 176 | "baidu.com", 177 | "qq.com" 178 | ], 179 | "outboundTag": "direct" 180 | }, 181 | { 182 | "type": "field", 183 | "ip": [ 184 | "10.0.0.0/8", 185 | "::1/128" 186 | ], 187 | "outboundTag": "test" 188 | } 189 | ] 190 | }`, 191 | Parser: createParser(), 192 | Output: &router.Config{ 193 | DomainStrategy: router.Config_AsIs, 194 | Rule: []*router.RoutingRule{ 195 | { 196 | Domain: []*router.Domain{ 197 | { 198 | Type: router.Domain_Plain, 199 | Value: "baidu.com", 200 | }, 201 | { 202 | Type: router.Domain_Plain, 203 | Value: "qq.com", 204 | }, 205 | }, 206 | TargetTag: &router.RoutingRule_Tag{ 207 | Tag: "direct", 208 | }, 209 | }, 210 | { 211 | Geoip: []*router.GeoIP{ 212 | { 213 | Cidr: []*router.CIDR{ 214 | { 215 | Ip: []byte{10, 0, 0, 0}, 216 | Prefix: 8, 217 | }, 218 | { 219 | Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 220 | Prefix: 128, 221 | }, 222 | }, 223 | }, 224 | }, 225 | TargetTag: &router.RoutingRule_Tag{ 226 | Tag: "test", 227 | }, 228 | }, 229 | }, 230 | }, 231 | }, 232 | }) 233 | } 234 | -------------------------------------------------------------------------------- /bazel/zip.bzl: -------------------------------------------------------------------------------- 1 | # Copied from google/nomulus project as we don't want to import the whole repository. 2 | 3 | ZIPPER = "@bazel_tools//tools/zip:zipper" 4 | 5 | def long_path(ctx, file_): 6 | """Constructs canonical runfile path relative to TEST_SRCDIR. 7 | Args: 8 | ctx: A Skylark rule context. 9 | file_: A File object that should appear in the runfiles for the test. 10 | Returns: 11 | A string path relative to TEST_SRCDIR suitable for use in tests and 12 | testing infrastructure. 13 | """ 14 | if file_.short_path.startswith("../"): 15 | return file_.short_path[3:] 16 | if file_.owner and file_.owner.workspace_root: 17 | return file_.owner.workspace_root + "/" + file_.short_path 18 | return ctx.workspace_name + "/" + file_.short_path 19 | 20 | def collect_runfiles(targets): 21 | """Aggregates runfiles from targets. 22 | Args: 23 | targets: A list of Bazel targets. 24 | Returns: 25 | A list of Bazel files. 26 | """ 27 | data = depset() 28 | for target in targets: 29 | if hasattr(target, "runfiles"): 30 | data += target.runfiles.files 31 | continue 32 | if hasattr(target, "data_runfiles"): 33 | data += target.data_runfiles.files 34 | if hasattr(target, "default_runfiles"): 35 | data += target.default_runfiles.files 36 | return data 37 | 38 | def _get_runfiles(target, attribute): 39 | runfiles = getattr(target, attribute, None) 40 | if runfiles: 41 | return runfiles.files 42 | return [] 43 | 44 | def _zip_file(ctx): 45 | """Implementation of zip_file() rule.""" 46 | for s, d in ctx.attr.mappings.items(): 47 | if (s.startswith("/") or s.endswith("/") or 48 | d.startswith("/") or d.endswith("/")): 49 | fail("mappings should not begin or end with slash") 50 | srcs = depset() 51 | srcs += ctx.files.srcs 52 | srcs += ctx.files.data 53 | srcs += collect_runfiles(ctx.attr.data) 54 | mapped = _map_sources(ctx, srcs, ctx.attr.mappings) 55 | cmd = [ 56 | "#!/bin/sh", 57 | "set -e", 58 | 'repo="$(pwd)"', 59 | 'zipper="${repo}/%s"' % ctx.file._zipper.path, 60 | 'archive="${repo}/%s"' % ctx.outputs.out.path, 61 | 'tmp="$(mktemp -d "${TMPDIR:-/tmp}/zip_file.XXXXXXXXXX")"', 62 | 'cd "${tmp}"', 63 | ] 64 | cmd += [ 65 | '"${zipper}" x "${repo}/%s"' % dep.zip_file.path 66 | for dep in ctx.attr.deps 67 | ] 68 | cmd += ["rm %s" % filename for filename in ctx.attr.exclude] 69 | cmd += [ 70 | 'mkdir -p "${tmp}/%s"' % zip_path 71 | for zip_path in depset( 72 | [ 73 | zip_path[:zip_path.rindex("/")] 74 | for _, zip_path in mapped 75 | if "/" in zip_path 76 | ], 77 | ) 78 | ] 79 | cmd += [ 80 | 'ln -sf "${repo}/%s" "${tmp}/%s"' % (path, zip_path) 81 | for path, zip_path in mapped 82 | ] 83 | cmd += [ 84 | ("find . | sed 1d | cut -c 3- | LC_ALL=C sort" + 85 | ' | xargs "${zipper}" cC "${archive}"'), 86 | 'cd "${repo}"', 87 | 'rm -rf "${tmp}"', 88 | ] 89 | script = ctx.new_file(ctx.bin_dir, "%s.sh" % ctx.label.name) 90 | ctx.file_action(output = script, content = "\n".join(cmd), executable = True) 91 | inputs = [ctx.file._zipper] 92 | inputs += [dep.zip_file for dep in ctx.attr.deps] 93 | inputs += list(srcs) 94 | ctx.action( 95 | inputs = inputs, 96 | outputs = [ctx.outputs.out], 97 | executable = script, 98 | mnemonic = "zip", 99 | progress_message = "Creating zip with %d inputs %s" % ( 100 | len(inputs), 101 | ctx.label, 102 | ), 103 | ) 104 | return struct(files = depset([ctx.outputs.out]), zip_file = ctx.outputs.out) 105 | 106 | def _map_sources(ctx, srcs, mappings): 107 | """Calculates paths in zip file for srcs.""" 108 | 109 | # order mappings with more path components first 110 | mappings = sorted([ 111 | (-len(source.split("/")), source, dest) 112 | for source, dest in mappings.items() 113 | ]) 114 | 115 | # get rid of the integer part of tuple used for sorting 116 | mappings = [(source, dest) for _, source, dest in mappings] 117 | mappings_indexes = range(len(mappings)) 118 | used = {i: False for i in mappings_indexes} 119 | mapped = [] 120 | for file_ in srcs: 121 | run_path = long_path(ctx, file_) 122 | zip_path = None 123 | for i in mappings_indexes: 124 | source = mappings[i][0] 125 | dest = mappings[i][1] 126 | if not source: 127 | if dest: 128 | zip_path = dest + "/" + run_path 129 | else: 130 | zip_path = run_path 131 | elif source == run_path: 132 | if dest: 133 | zip_path = dest 134 | else: 135 | zip_path = run_path 136 | elif run_path.startswith(source + "/"): 137 | if dest: 138 | zip_path = dest + run_path[len(source):] 139 | else: 140 | zip_path = run_path[len(source) + 1:] 141 | else: 142 | continue 143 | used[i] = True 144 | break 145 | if not zip_path: 146 | fail("no mapping matched: " + run_path) 147 | mapped += [(file_.path, zip_path)] 148 | for i in mappings_indexes: 149 | if not used[i]: 150 | fail('superfluous mapping: "%s" -> "%s"' % mappings[i]) 151 | return mapped 152 | 153 | pkg_zip = rule( 154 | implementation = _zip_file, 155 | attrs = { 156 | "out": attr.output(mandatory = True), 157 | "srcs": attr.label_list(allow_files = True), 158 | "data": attr.label_list(allow_files = True), 159 | "deps": attr.label_list(providers = ["zip_file"]), 160 | "exclude": attr.string_list(), 161 | "mappings": attr.string_dict(), 162 | "_zipper": attr.label(default = Label(ZIPPER), single_file = True), 163 | }, 164 | ) 165 | -------------------------------------------------------------------------------- /tools/conf/transport_authenticators.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/golang/protobuf/proto" 7 | 8 | "v2ray.com/core/transport/internet/headers/http" 9 | "v2ray.com/core/transport/internet/headers/noop" 10 | "v2ray.com/core/transport/internet/headers/srtp" 11 | "v2ray.com/core/transport/internet/headers/tls" 12 | "v2ray.com/core/transport/internet/headers/utp" 13 | "v2ray.com/core/transport/internet/headers/wechat" 14 | "v2ray.com/core/transport/internet/headers/wireguard" 15 | ) 16 | 17 | type NoOpAuthenticator struct{} 18 | 19 | func (NoOpAuthenticator) Build() (proto.Message, error) { 20 | return new(noop.Config), nil 21 | } 22 | 23 | type NoOpConnectionAuthenticator struct{} 24 | 25 | func (NoOpConnectionAuthenticator) Build() (proto.Message, error) { 26 | return new(noop.ConnectionConfig), nil 27 | } 28 | 29 | type SRTPAuthenticator struct{} 30 | 31 | func (SRTPAuthenticator) Build() (proto.Message, error) { 32 | return new(srtp.Config), nil 33 | } 34 | 35 | type UTPAuthenticator struct{} 36 | 37 | func (UTPAuthenticator) Build() (proto.Message, error) { 38 | return new(utp.Config), nil 39 | } 40 | 41 | type WechatVideoAuthenticator struct{} 42 | 43 | func (WechatVideoAuthenticator) Build() (proto.Message, error) { 44 | return new(wechat.VideoConfig), nil 45 | } 46 | 47 | type WireguardAuthenticator struct{} 48 | 49 | func (WireguardAuthenticator) Build() (proto.Message, error) { 50 | return new(wireguard.WireguardConfig), nil 51 | } 52 | 53 | type DTLSAuthenticator struct{} 54 | 55 | func (DTLSAuthenticator) Build() (proto.Message, error) { 56 | return new(tls.PacketConfig), nil 57 | } 58 | 59 | type HTTPAuthenticatorRequest struct { 60 | Version string `json:"version"` 61 | Method string `json:"method"` 62 | Path StringList `json:"path"` 63 | Headers map[string]*StringList `json:"headers"` 64 | } 65 | 66 | func sortMapKeys(m map[string]*StringList) []string { 67 | var keys []string 68 | for key := range m { 69 | keys = append(keys, key) 70 | } 71 | sort.Strings(keys) 72 | return keys 73 | } 74 | 75 | func (v *HTTPAuthenticatorRequest) Build() (*http.RequestConfig, error) { 76 | config := &http.RequestConfig{ 77 | Uri: []string{"/"}, 78 | Header: []*http.Header{ 79 | { 80 | Name: "Host", 81 | Value: []string{"www.baidu.com", "www.bing.com"}, 82 | }, 83 | { 84 | Name: "User-Agent", 85 | Value: []string{ 86 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", 87 | "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46", 88 | }, 89 | }, 90 | { 91 | Name: "Accept-Encoding", 92 | Value: []string{"gzip, deflate"}, 93 | }, 94 | { 95 | Name: "Connection", 96 | Value: []string{"keep-alive"}, 97 | }, 98 | { 99 | Name: "Pragma", 100 | Value: []string{"no-cache"}, 101 | }, 102 | }, 103 | } 104 | 105 | if len(v.Version) > 0 { 106 | config.Version = &http.Version{Value: v.Version} 107 | } 108 | 109 | if len(v.Method) > 0 { 110 | config.Method = &http.Method{Value: v.Method} 111 | } 112 | 113 | if len(v.Path) > 0 { 114 | config.Uri = append([]string(nil), (v.Path)...) 115 | } 116 | 117 | if len(v.Headers) > 0 { 118 | config.Header = make([]*http.Header, 0, len(v.Headers)) 119 | headerNames := sortMapKeys(v.Headers) 120 | for _, key := range headerNames { 121 | value := v.Headers[key] 122 | if value == nil { 123 | return nil, newError("empty HTTP header value: " + key).AtError() 124 | } 125 | config.Header = append(config.Header, &http.Header{ 126 | Name: key, 127 | Value: append([]string(nil), (*value)...), 128 | }) 129 | } 130 | } 131 | 132 | return config, nil 133 | } 134 | 135 | type HTTPAuthenticatorResponse struct { 136 | Version string `json:"version"` 137 | Status string `json:"status"` 138 | Reason string `json:"reason"` 139 | Headers map[string]*StringList `json:"headers"` 140 | } 141 | 142 | func (v *HTTPAuthenticatorResponse) Build() (*http.ResponseConfig, error) { 143 | config := &http.ResponseConfig{ 144 | Header: []*http.Header{ 145 | { 146 | Name: "Content-Type", 147 | Value: []string{"application/octet-stream", "video/mpeg"}, 148 | }, 149 | { 150 | Name: "Transfer-Encoding", 151 | Value: []string{"chunked"}, 152 | }, 153 | { 154 | Name: "Connection", 155 | Value: []string{"keep-alive"}, 156 | }, 157 | { 158 | Name: "Pragma", 159 | Value: []string{"no-cache"}, 160 | }, 161 | { 162 | Name: "Cache-Control", 163 | Value: []string{"private", "no-cache"}, 164 | }, 165 | }, 166 | } 167 | 168 | if len(v.Version) > 0 { 169 | config.Version = &http.Version{Value: v.Version} 170 | } 171 | 172 | if len(v.Status) > 0 || len(v.Reason) > 0 { 173 | config.Status = &http.Status{ 174 | Code: "200", 175 | Reason: "OK", 176 | } 177 | if len(v.Status) > 0 { 178 | config.Status.Code = v.Status 179 | } 180 | if len(v.Reason) > 0 { 181 | config.Status.Reason = v.Reason 182 | } 183 | } 184 | 185 | if len(v.Headers) > 0 { 186 | config.Header = make([]*http.Header, 0, len(v.Headers)) 187 | headerNames := sortMapKeys(v.Headers) 188 | for _, key := range headerNames { 189 | value := v.Headers[key] 190 | if value == nil { 191 | return nil, newError("empty HTTP header value: " + key).AtError() 192 | } 193 | config.Header = append(config.Header, &http.Header{ 194 | Name: key, 195 | Value: append([]string(nil), (*value)...), 196 | }) 197 | } 198 | } 199 | 200 | return config, nil 201 | } 202 | 203 | type HTTPAuthenticator struct { 204 | Request HTTPAuthenticatorRequest `json:"request"` 205 | Response HTTPAuthenticatorResponse `json:"response"` 206 | } 207 | 208 | func (v *HTTPAuthenticator) Build() (proto.Message, error) { 209 | config := new(http.Config) 210 | requestConfig, err := v.Request.Build() 211 | if err != nil { 212 | return nil, err 213 | } 214 | config.Request = requestConfig 215 | 216 | responseConfig, err := v.Response.Build() 217 | if err != nil { 218 | return nil, err 219 | } 220 | config.Response = responseConfig 221 | 222 | return config, nil 223 | } 224 | -------------------------------------------------------------------------------- /tools/conf/v2ray_test.go: -------------------------------------------------------------------------------- 1 | package conf_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "v2ray.com/core" 10 | "v2ray.com/core/app/dispatcher" 11 | "v2ray.com/core/app/log" 12 | "v2ray.com/core/app/proxyman" 13 | "v2ray.com/core/app/router" 14 | clog "v2ray.com/core/common/log" 15 | "v2ray.com/core/common/net" 16 | "v2ray.com/core/common/protocol" 17 | "v2ray.com/core/common/serial" 18 | "v2ray.com/core/proxy/blackhole" 19 | dns_proxy "v2ray.com/core/proxy/dns" 20 | "v2ray.com/core/proxy/freedom" 21 | "v2ray.com/core/proxy/vmess" 22 | "v2ray.com/core/proxy/vmess/inbound" 23 | "v2ray.com/core/transport/internet" 24 | "v2ray.com/core/transport/internet/http" 25 | "v2ray.com/core/transport/internet/tls" 26 | "v2ray.com/core/transport/internet/websocket" 27 | . "v2ray.com/ext/tools/conf" 28 | ) 29 | 30 | func TestV2RayConfig(t *testing.T) { 31 | createParser := func() func(string) (proto.Message, error) { 32 | return func(s string) (proto.Message, error) { 33 | config := new(Config) 34 | if err := json.Unmarshal([]byte(s), config); err != nil { 35 | return nil, err 36 | } 37 | return config.Build() 38 | } 39 | } 40 | 41 | runMultiTestCase(t, []TestCase{ 42 | { 43 | Input: `{ 44 | "outbound": { 45 | "protocol": "freedom", 46 | "settings": {} 47 | }, 48 | "log": { 49 | "access": "/var/log/v2ray/access.log", 50 | "loglevel": "error", 51 | "error": "/var/log/v2ray/error.log" 52 | }, 53 | "inbound": { 54 | "streamSettings": { 55 | "network": "ws", 56 | "wsSettings": { 57 | "headers": { 58 | "host": "example.domain" 59 | }, 60 | "path": "" 61 | }, 62 | "tlsSettings": { 63 | "alpn": "h2" 64 | }, 65 | "security": "tls" 66 | }, 67 | "protocol": "vmess", 68 | "port": 443, 69 | "settings": { 70 | "clients": [ 71 | { 72 | "alterId": 100, 73 | "security": "aes-128-gcm", 74 | "id": "0cdf8a45-303d-4fed-9780-29aa7f54175e" 75 | } 76 | ] 77 | } 78 | }, 79 | "inbounds": [{ 80 | "streamSettings": { 81 | "network": "ws", 82 | "wsSettings": { 83 | "headers": { 84 | "host": "example.domain" 85 | }, 86 | "path": "" 87 | }, 88 | "tlsSettings": { 89 | "alpn": "h2" 90 | }, 91 | "security": "tls" 92 | }, 93 | "protocol": "vmess", 94 | "port": "443-500", 95 | "allocate": { 96 | "strategy": "random", 97 | "concurrency": 3 98 | }, 99 | "settings": { 100 | "clients": [ 101 | { 102 | "alterId": 100, 103 | "security": "aes-128-gcm", 104 | "id": "0cdf8a45-303d-4fed-9780-29aa7f54175e" 105 | } 106 | ] 107 | } 108 | }], 109 | "outboundDetour": [ 110 | { 111 | "tag": "blocked", 112 | "protocol": "blackhole" 113 | }, 114 | { 115 | "protocol": "dns" 116 | } 117 | ], 118 | "routing": { 119 | "strategy": "rules", 120 | "settings": { 121 | "rules": [ 122 | { 123 | "ip": [ 124 | "10.0.0.0/8" 125 | ], 126 | "type": "field", 127 | "outboundTag": "blocked" 128 | } 129 | ] 130 | } 131 | }, 132 | "transport": { 133 | "httpSettings": { 134 | "path": "/test" 135 | } 136 | } 137 | }`, 138 | Parser: createParser(), 139 | Output: &core.Config{ 140 | App: []*serial.TypedMessage{ 141 | serial.ToTypedMessage(&dispatcher.Config{}), 142 | serial.ToTypedMessage(&proxyman.InboundConfig{}), 143 | serial.ToTypedMessage(&proxyman.OutboundConfig{}), 144 | serial.ToTypedMessage(&log.Config{ 145 | ErrorLogType: log.LogType_File, 146 | ErrorLogPath: "/var/log/v2ray/error.log", 147 | ErrorLogLevel: clog.Severity_Error, 148 | AccessLogType: log.LogType_File, 149 | AccessLogPath: "/var/log/v2ray/access.log", 150 | }), 151 | serial.ToTypedMessage(&router.Config{ 152 | DomainStrategy: router.Config_AsIs, 153 | Rule: []*router.RoutingRule{ 154 | { 155 | Geoip: []*router.GeoIP{ 156 | { 157 | Cidr: []*router.CIDR{ 158 | { 159 | Ip: []byte{10, 0, 0, 0}, 160 | Prefix: 8, 161 | }, 162 | }, 163 | }, 164 | }, 165 | TargetTag: &router.RoutingRule_Tag{ 166 | Tag: "blocked", 167 | }, 168 | }, 169 | }, 170 | }), 171 | }, 172 | Outbound: []*core.OutboundHandlerConfig{ 173 | { 174 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ 175 | StreamSettings: &internet.StreamConfig{ 176 | ProtocolName: "tcp", 177 | TransportSettings: []*internet.TransportConfig{ 178 | { 179 | ProtocolName: "http", 180 | Settings: serial.ToTypedMessage(&http.Config{ 181 | Path: "/test", 182 | }), 183 | }, 184 | }, 185 | }, 186 | }), 187 | ProxySettings: serial.ToTypedMessage(&freedom.Config{ 188 | DomainStrategy: freedom.Config_AS_IS, 189 | Timeout: 600, 190 | UserLevel: 0, 191 | }), 192 | }, 193 | { 194 | Tag: "blocked", 195 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ 196 | StreamSettings: &internet.StreamConfig{ 197 | ProtocolName: "tcp", 198 | TransportSettings: []*internet.TransportConfig{ 199 | { 200 | ProtocolName: "http", 201 | Settings: serial.ToTypedMessage(&http.Config{ 202 | Path: "/test", 203 | }), 204 | }, 205 | }, 206 | }, 207 | }), 208 | ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), 209 | }, 210 | { 211 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ 212 | StreamSettings: &internet.StreamConfig{ 213 | ProtocolName: "tcp", 214 | TransportSettings: []*internet.TransportConfig{ 215 | { 216 | ProtocolName: "http", 217 | Settings: serial.ToTypedMessage(&http.Config{ 218 | Path: "/test", 219 | }), 220 | }, 221 | }, 222 | }, 223 | }), 224 | ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}), 225 | }, 226 | }, 227 | Inbound: []*core.InboundHandlerConfig{ 228 | { 229 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ 230 | PortRange: &net.PortRange{ 231 | From: 443, 232 | To: 443, 233 | }, 234 | StreamSettings: &internet.StreamConfig{ 235 | ProtocolName: "websocket", 236 | TransportSettings: []*internet.TransportConfig{ 237 | { 238 | ProtocolName: "websocket", 239 | Settings: serial.ToTypedMessage(&websocket.Config{ 240 | Header: []*websocket.Header{ 241 | { 242 | Key: "host", 243 | Value: "example.domain", 244 | }, 245 | }, 246 | }), 247 | }, 248 | { 249 | ProtocolName: "http", 250 | Settings: serial.ToTypedMessage(&http.Config{ 251 | Path: "/test", 252 | }), 253 | }, 254 | }, 255 | SecurityType: "v2ray.core.transport.internet.tls.Config", 256 | SecuritySettings: []*serial.TypedMessage{ 257 | serial.ToTypedMessage(&tls.Config{ 258 | NextProtocol: []string{"h2"}, 259 | }), 260 | }, 261 | }, 262 | }), 263 | ProxySettings: serial.ToTypedMessage(&inbound.Config{ 264 | User: []*protocol.User{ 265 | { 266 | Level: 0, 267 | Account: serial.ToTypedMessage(&vmess.Account{ 268 | Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e", 269 | AlterId: 100, 270 | SecuritySettings: &protocol.SecurityConfig{ 271 | Type: protocol.SecurityType_AES128_GCM, 272 | }, 273 | }), 274 | }, 275 | }, 276 | }), 277 | }, 278 | { 279 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ 280 | PortRange: &net.PortRange{ 281 | From: 443, 282 | To: 500, 283 | }, 284 | AllocationStrategy: &proxyman.AllocationStrategy{ 285 | Type: proxyman.AllocationStrategy_Random, 286 | Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ 287 | Value: 3, 288 | }, 289 | }, 290 | StreamSettings: &internet.StreamConfig{ 291 | ProtocolName: "websocket", 292 | TransportSettings: []*internet.TransportConfig{ 293 | { 294 | ProtocolName: "websocket", 295 | Settings: serial.ToTypedMessage(&websocket.Config{ 296 | Header: []*websocket.Header{ 297 | { 298 | Key: "host", 299 | Value: "example.domain", 300 | }, 301 | }, 302 | }), 303 | }, 304 | { 305 | ProtocolName: "http", 306 | Settings: serial.ToTypedMessage(&http.Config{ 307 | Path: "/test", 308 | }), 309 | }, 310 | }, 311 | SecurityType: "v2ray.core.transport.internet.tls.Config", 312 | SecuritySettings: []*serial.TypedMessage{ 313 | serial.ToTypedMessage(&tls.Config{ 314 | NextProtocol: []string{"h2"}, 315 | }), 316 | }, 317 | }, 318 | }), 319 | ProxySettings: serial.ToTypedMessage(&inbound.Config{ 320 | User: []*protocol.User{ 321 | { 322 | Level: 0, 323 | Account: serial.ToTypedMessage(&vmess.Account{ 324 | Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e", 325 | AlterId: 100, 326 | SecuritySettings: &protocol.SecurityConfig{ 327 | Type: protocol.SecurityType_AES128_GCM, 328 | }, 329 | }), 330 | }, 331 | }, 332 | }), 333 | }, 334 | }, 335 | }, 336 | }, 337 | }) 338 | } 339 | -------------------------------------------------------------------------------- /tools/conf/router.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "strings" 7 | 8 | "v2ray.com/core/app/router" 9 | "v2ray.com/core/common/net" 10 | "v2ray.com/ext/sysio" 11 | 12 | "github.com/golang/protobuf/proto" 13 | ) 14 | 15 | type RouterRulesConfig struct { 16 | RuleList []json.RawMessage `json:"rules"` 17 | DomainStrategy string `json:"domainStrategy"` 18 | } 19 | 20 | type BalancingRule struct { 21 | Tag string `json:"tag"` 22 | Selectors StringList `json:"selector"` 23 | } 24 | 25 | func (r *BalancingRule) Build() (*router.BalancingRule, error) { 26 | if len(r.Tag) == 0 { 27 | return nil, newError("empty balancer tag") 28 | } 29 | if len(r.Selectors) == 0 { 30 | return nil, newError("empty selector list") 31 | } 32 | 33 | return &router.BalancingRule{ 34 | Tag: r.Tag, 35 | OutboundSelector: []string(r.Selectors), 36 | }, nil 37 | } 38 | 39 | type RouterConfig struct { 40 | Settings *RouterRulesConfig `json:"settings"` // Deprecated 41 | RuleList []json.RawMessage `json:"rules"` 42 | DomainStrategy *string `json:"domainStrategy"` 43 | Balancers []*BalancingRule `json:"balancers"` 44 | } 45 | 46 | func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy { 47 | ds := "" 48 | if c.DomainStrategy != nil { 49 | ds = *c.DomainStrategy 50 | } else if c.Settings != nil { 51 | ds = c.Settings.DomainStrategy 52 | } 53 | 54 | switch strings.ToLower(ds) { 55 | case "alwaysip": 56 | return router.Config_UseIp 57 | case "ipifnonmatch": 58 | return router.Config_IpIfNonMatch 59 | case "ipondemand": 60 | return router.Config_IpOnDemand 61 | default: 62 | return router.Config_AsIs 63 | } 64 | } 65 | 66 | func (c *RouterConfig) Build() (*router.Config, error) { 67 | config := new(router.Config) 68 | config.DomainStrategy = c.getDomainStrategy() 69 | 70 | rawRuleList := c.RuleList 71 | if c.Settings != nil { 72 | rawRuleList = append(c.RuleList, c.Settings.RuleList...) 73 | } 74 | for _, rawRule := range rawRuleList { 75 | rule, err := ParseRule(rawRule) 76 | if err != nil { 77 | return nil, err 78 | } 79 | config.Rule = append(config.Rule, rule) 80 | } 81 | for _, rawBalancer := range c.Balancers { 82 | balancer, err := rawBalancer.Build() 83 | if err != nil { 84 | return nil, err 85 | } 86 | config.BalancingRule = append(config.BalancingRule, balancer) 87 | } 88 | return config, nil 89 | } 90 | 91 | type RouterRule struct { 92 | Type string `json:"type"` 93 | OutboundTag string `json:"outboundTag"` 94 | BalancerTag string `json:"balancerTag"` 95 | } 96 | 97 | func ParseIP(s string) (*router.CIDR, error) { 98 | var addr, mask string 99 | i := strings.Index(s, "/") 100 | if i < 0 { 101 | addr = s 102 | } else { 103 | addr = s[:i] 104 | mask = s[i+1:] 105 | } 106 | ip := net.ParseAddress(addr) 107 | switch ip.Family() { 108 | case net.AddressFamilyIPv4: 109 | bits := uint32(32) 110 | if len(mask) > 0 { 111 | bits64, err := strconv.ParseUint(mask, 10, 32) 112 | if err != nil { 113 | return nil, newError("invalid network mask for router: ", mask).Base(err) 114 | } 115 | bits = uint32(bits64) 116 | } 117 | if bits > 32 { 118 | return nil, newError("invalid network mask for router: ", bits) 119 | } 120 | return &router.CIDR{ 121 | Ip: []byte(ip.IP()), 122 | Prefix: bits, 123 | }, nil 124 | case net.AddressFamilyIPv6: 125 | bits := uint32(128) 126 | if len(mask) > 0 { 127 | bits64, err := strconv.ParseUint(mask, 10, 32) 128 | if err != nil { 129 | return nil, newError("invalid network mask for router: ", mask).Base(err) 130 | } 131 | bits = uint32(bits64) 132 | } 133 | if bits > 128 { 134 | return nil, newError("invalid network mask for router: ", bits) 135 | } 136 | return &router.CIDR{ 137 | Ip: []byte(ip.IP()), 138 | Prefix: bits, 139 | }, nil 140 | default: 141 | return nil, newError("unsupported address for router: ", s) 142 | } 143 | } 144 | 145 | func loadGeoIP(country string) ([]*router.CIDR, error) { 146 | return loadIP("geoip.dat", country) 147 | } 148 | 149 | func loadIP(filename, country string) ([]*router.CIDR, error) { 150 | geoipBytes, err := sysio.ReadAsset(filename) 151 | if err != nil { 152 | return nil, newError("failed to open file: ", filename).Base(err) 153 | } 154 | var geoipList router.GeoIPList 155 | if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { 156 | return nil, err 157 | } 158 | 159 | for _, geoip := range geoipList.Entry { 160 | if geoip.CountryCode == country { 161 | return geoip.Cidr, nil 162 | } 163 | } 164 | 165 | return nil, newError("country not found: " + country) 166 | } 167 | 168 | func loadSite(filename, country string) ([]*router.Domain, error) { 169 | geositeBytes, err := sysio.ReadAsset(filename) 170 | if err != nil { 171 | return nil, newError("failed to open file: ", filename).Base(err) 172 | } 173 | var geositeList router.GeoSiteList 174 | if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { 175 | return nil, err 176 | } 177 | 178 | for _, site := range geositeList.Entry { 179 | if site.CountryCode == country { 180 | return site.Domain, nil 181 | } 182 | } 183 | 184 | return nil, newError("country not found: " + country) 185 | } 186 | 187 | type AttributeMatcher interface { 188 | Match(*router.Domain) bool 189 | } 190 | 191 | type BooleanMatcher string 192 | 193 | func (m BooleanMatcher) Match(domain *router.Domain) bool { 194 | for _, attr := range domain.Attribute { 195 | if attr.Key == string(m) { 196 | return true 197 | } 198 | } 199 | return false 200 | } 201 | 202 | type AttributeList struct { 203 | matcher []AttributeMatcher 204 | } 205 | 206 | func (al *AttributeList) Match(domain *router.Domain) bool { 207 | for _, matcher := range al.matcher { 208 | if !matcher.Match(domain) { 209 | return false 210 | } 211 | } 212 | return true 213 | } 214 | 215 | func (al *AttributeList) IsEmpty() bool { 216 | return len(al.matcher) == 0 217 | } 218 | 219 | func parseAttrs(attrs []string) *AttributeList { 220 | al := new(AttributeList) 221 | for _, attr := range attrs { 222 | lc := strings.ToLower(attr) 223 | al.matcher = append(al.matcher, BooleanMatcher(lc)) 224 | } 225 | return al 226 | } 227 | 228 | func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { 229 | parts := strings.Split(siteWithAttr, "@") 230 | if len(parts) == 0 { 231 | return nil, newError("empty site") 232 | } 233 | country := strings.ToUpper(parts[0]) 234 | attrs := parseAttrs(parts[1:]) 235 | domains, err := loadSite(file, country) 236 | if err != nil { 237 | return nil, err 238 | } 239 | 240 | if attrs.IsEmpty() { 241 | return domains, nil 242 | } 243 | 244 | filteredDomains := make([]*router.Domain, 0, len(domains)) 245 | for _, domain := range domains { 246 | if attrs.Match(domain) { 247 | filteredDomains = append(filteredDomains, domain) 248 | } 249 | } 250 | 251 | return filteredDomains, nil 252 | } 253 | 254 | func parseDomainRule(domain string) ([]*router.Domain, error) { 255 | if strings.HasPrefix(domain, "geosite:") { 256 | country := strings.ToUpper(domain[8:]) 257 | domains, err := loadGeositeWithAttr("geosite.dat", country) 258 | if err != nil { 259 | return nil, newError("failed to load geosite: ", country).Base(err) 260 | } 261 | return domains, nil 262 | } 263 | 264 | if strings.HasPrefix(domain, "ext:") { 265 | kv := strings.Split(domain[4:], ":") 266 | if len(kv) != 2 { 267 | return nil, newError("invalid external resource: ", domain) 268 | } 269 | filename := kv[0] 270 | country := kv[1] 271 | domains, err := loadGeositeWithAttr(filename, country) 272 | if err != nil { 273 | return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err) 274 | } 275 | return domains, nil 276 | } 277 | 278 | domainRule := new(router.Domain) 279 | switch { 280 | case strings.HasPrefix(domain, "regexp:"): 281 | domainRule.Type = router.Domain_Regex 282 | domainRule.Value = domain[7:] 283 | case strings.HasPrefix(domain, "domain:"): 284 | domainRule.Type = router.Domain_Domain 285 | domainRule.Value = domain[7:] 286 | case strings.HasPrefix(domain, "full:"): 287 | domainRule.Type = router.Domain_Full 288 | domainRule.Value = domain[5:] 289 | default: 290 | domainRule.Type = router.Domain_Plain 291 | domainRule.Value = domain 292 | } 293 | return []*router.Domain{domainRule}, nil 294 | } 295 | 296 | func toCidrList(ips StringList) ([]*router.GeoIP, error) { 297 | var geoipList []*router.GeoIP 298 | var customCidrs []*router.CIDR 299 | 300 | for _, ip := range ips { 301 | if strings.HasPrefix(ip, "geoip:") { 302 | country := ip[6:] 303 | geoip, err := loadGeoIP(strings.ToUpper(country)) 304 | if err != nil { 305 | return nil, newError("failed to load GeoIP: ", country).Base(err) 306 | } 307 | 308 | geoipList = append(geoipList, &router.GeoIP{ 309 | CountryCode: strings.ToUpper(country), 310 | Cidr: geoip, 311 | }) 312 | continue 313 | } 314 | 315 | if strings.HasPrefix(ip, "ext:") { 316 | kv := strings.Split(ip[4:], ":") 317 | if len(kv) != 2 { 318 | return nil, newError("invalid external resource: ", ip) 319 | } 320 | 321 | filename := kv[0] 322 | country := kv[1] 323 | geoip, err := loadGeoIP(strings.ToUpper(country)) 324 | if err != nil { 325 | return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err) 326 | } 327 | 328 | geoipList = append(geoipList, &router.GeoIP{ 329 | CountryCode: strings.ToUpper(filename + "_" + country), 330 | Cidr: geoip, 331 | }) 332 | 333 | continue 334 | } 335 | 336 | ipRule, err := ParseIP(ip) 337 | if err != nil { 338 | return nil, newError("invalid IP: ", ip).Base(err) 339 | } 340 | customCidrs = append(customCidrs, ipRule) 341 | } 342 | 343 | if len(customCidrs) > 0 { 344 | geoipList = append(geoipList, &router.GeoIP{ 345 | Cidr: customCidrs, 346 | }) 347 | } 348 | 349 | return geoipList, nil 350 | } 351 | 352 | func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { 353 | type RawFieldRule struct { 354 | RouterRule 355 | Domain *StringList `json:"domain"` 356 | IP *StringList `json:"ip"` 357 | Port *PortRange `json:"port"` 358 | Network *NetworkList `json:"network"` 359 | SourceIP *StringList `json:"source"` 360 | User *StringList `json:"user"` 361 | InboundTag *StringList `json:"inboundTag"` 362 | Protocols *StringList `json:"protocol"` 363 | } 364 | rawFieldRule := new(RawFieldRule) 365 | err := json.Unmarshal(msg, rawFieldRule) 366 | if err != nil { 367 | return nil, err 368 | } 369 | 370 | rule := new(router.RoutingRule) 371 | if len(rawFieldRule.OutboundTag) > 0 { 372 | rule.TargetTag = &router.RoutingRule_Tag{ 373 | Tag: rawFieldRule.OutboundTag, 374 | } 375 | } else if len(rawFieldRule.BalancerTag) > 0 { 376 | rule.TargetTag = &router.RoutingRule_BalancingTag{ 377 | BalancingTag: rawFieldRule.BalancerTag, 378 | } 379 | } else { 380 | return nil, newError("neither outboundTag nor balancerTag is specified in routing rule") 381 | } 382 | 383 | if rawFieldRule.Domain != nil { 384 | for _, domain := range *rawFieldRule.Domain { 385 | rules, err := parseDomainRule(domain) 386 | if err != nil { 387 | return nil, newError("failed to parse domain rule: ", domain).Base(err) 388 | } 389 | rule.Domain = append(rule.Domain, rules...) 390 | } 391 | } 392 | 393 | if rawFieldRule.IP != nil { 394 | geoipList, err := toCidrList(*rawFieldRule.IP) 395 | if err != nil { 396 | return nil, err 397 | } 398 | rule.Geoip = geoipList 399 | } 400 | 401 | if rawFieldRule.Port != nil { 402 | rule.PortRange = rawFieldRule.Port.Build() 403 | } 404 | 405 | if rawFieldRule.Network != nil { 406 | rule.Networks = rawFieldRule.Network.Build() 407 | } 408 | 409 | if rawFieldRule.SourceIP != nil { 410 | geoipList, err := toCidrList(*rawFieldRule.SourceIP) 411 | if err != nil { 412 | return nil, err 413 | } 414 | rule.SourceGeoip = geoipList 415 | } 416 | 417 | if rawFieldRule.User != nil { 418 | for _, s := range *rawFieldRule.User { 419 | rule.UserEmail = append(rule.UserEmail, s) 420 | } 421 | } 422 | 423 | if rawFieldRule.InboundTag != nil { 424 | for _, s := range *rawFieldRule.InboundTag { 425 | rule.InboundTag = append(rule.InboundTag, s) 426 | } 427 | } 428 | 429 | if rawFieldRule.Protocols != nil { 430 | for _, s := range *rawFieldRule.Protocols { 431 | rule.Protocol = append(rule.Protocol, s) 432 | } 433 | } 434 | 435 | return rule, nil 436 | } 437 | 438 | func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) { 439 | rawRule := new(RouterRule) 440 | err := json.Unmarshal(msg, rawRule) 441 | if err != nil { 442 | return nil, newError("invalid router rule").Base(err) 443 | } 444 | if rawRule.Type == "field" { 445 | fieldrule, err := parseFieldRule(msg) 446 | if err != nil { 447 | return nil, newError("invalid field rule").Base(err) 448 | } 449 | return fieldrule, nil 450 | } 451 | if rawRule.Type == "chinaip" { 452 | chinaiprule, err := parseChinaIPRule(msg) 453 | if err != nil { 454 | return nil, newError("invalid chinaip rule").Base(err) 455 | } 456 | return chinaiprule, nil 457 | } 458 | if rawRule.Type == "chinasites" { 459 | chinasitesrule, err := parseChinaSitesRule(msg) 460 | if err != nil { 461 | return nil, newError("invalid chinasites rule").Base(err) 462 | } 463 | return chinasitesrule, nil 464 | } 465 | return nil, newError("unknown router rule type: ", rawRule.Type) 466 | } 467 | 468 | func parseChinaIPRule(data []byte) (*router.RoutingRule, error) { 469 | rawRule := new(RouterRule) 470 | err := json.Unmarshal(data, rawRule) 471 | if err != nil { 472 | return nil, newError("invalid router rule").Base(err) 473 | } 474 | chinaIPs, err := loadGeoIP("CN") 475 | if err != nil { 476 | return nil, newError("failed to load geoip:cn").Base(err) 477 | } 478 | return &router.RoutingRule{ 479 | TargetTag: &router.RoutingRule_Tag{ 480 | Tag: rawRule.OutboundTag, 481 | }, 482 | Cidr: chinaIPs, 483 | }, nil 484 | } 485 | 486 | func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) { 487 | rawRule := new(RouterRule) 488 | err := json.Unmarshal(data, rawRule) 489 | if err != nil { 490 | return nil, newError("invalid router rule").Base(err).AtError() 491 | } 492 | domains, err := loadGeositeWithAttr("geosite.dat", "CN") 493 | if err != nil { 494 | return nil, newError("failed to load geosite:cn.").Base(err) 495 | } 496 | return &router.RoutingRule{ 497 | TargetTag: &router.RoutingRule_Tag{ 498 | Tag: rawRule.OutboundTag, 499 | }, 500 | Domain: domains, 501 | }, nil 502 | } 503 | -------------------------------------------------------------------------------- /tools/conf/v2ray.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "v2ray.com/core" 8 | "v2ray.com/core/app/dispatcher" 9 | "v2ray.com/core/app/proxyman" 10 | "v2ray.com/core/app/stats" 11 | "v2ray.com/core/common/serial" 12 | ) 13 | 14 | var ( 15 | inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ 16 | "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, 17 | "http": func() interface{} { return new(HttpServerConfig) }, 18 | "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, 19 | "socks": func() interface{} { return new(SocksServerConfig) }, 20 | "vmess": func() interface{} { return new(VMessInboundConfig) }, 21 | "mtproto": func() interface{} { return new(MTProtoServerConfig) }, 22 | }, "protocol", "settings") 23 | 24 | outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ 25 | "blackhole": func() interface{} { return new(BlackholeConfig) }, 26 | "freedom": func() interface{} { return new(FreedomConfig) }, 27 | "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, 28 | "vmess": func() interface{} { return new(VMessOutboundConfig) }, 29 | "socks": func() interface{} { return new(SocksClientConfig) }, 30 | "mtproto": func() interface{} { return new(MTProtoClientConfig) }, 31 | "dns": func() interface{} { return new(DnsOutboundConfig) }, 32 | }, "protocol", "settings") 33 | ) 34 | 35 | func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) { 36 | kp := make([]proxyman.KnownProtocols, 0, 8) 37 | for _, p := range s { 38 | switch strings.ToLower(p) { 39 | case "http": 40 | kp = append(kp, proxyman.KnownProtocols_HTTP) 41 | case "https", "tls", "ssl": 42 | kp = append(kp, proxyman.KnownProtocols_TLS) 43 | default: 44 | return nil, newError("Unknown protocol: ", p) 45 | } 46 | } 47 | return kp, nil 48 | } 49 | 50 | type SniffingConfig struct { 51 | Enabled bool `json:"enabled"` 52 | DestOverride *StringList `json:"destOverride"` 53 | } 54 | 55 | func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { 56 | var p []string 57 | if c.DestOverride != nil { 58 | for _, domainOverride := range *c.DestOverride { 59 | switch strings.ToLower(domainOverride) { 60 | case "http": 61 | p = append(p, "http") 62 | case "tls", "https", "ssl": 63 | p = append(p, "tls") 64 | default: 65 | return nil, newError("unknown protocol: ", domainOverride) 66 | } 67 | } 68 | } 69 | 70 | return &proxyman.SniffingConfig{ 71 | Enabled: c.Enabled, 72 | DestinationOverride: p, 73 | }, nil 74 | } 75 | 76 | type MuxConfig struct { 77 | Enabled bool `json:"enabled"` 78 | Concurrency uint16 `json:"concurrency"` 79 | } 80 | 81 | func (c *MuxConfig) GetConcurrency() uint16 { 82 | if c.Concurrency == 0 { 83 | return 8 84 | } 85 | return c.Concurrency 86 | } 87 | 88 | type InboundDetourAllocationConfig struct { 89 | Strategy string `json:"strategy"` 90 | Concurrency *uint32 `json:"concurrency"` 91 | RefreshMin *uint32 `json:"refresh"` 92 | } 93 | 94 | // Build implements Buildable. 95 | func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) { 96 | config := new(proxyman.AllocationStrategy) 97 | switch strings.ToLower(c.Strategy) { 98 | case "always": 99 | config.Type = proxyman.AllocationStrategy_Always 100 | case "random": 101 | config.Type = proxyman.AllocationStrategy_Random 102 | case "external": 103 | config.Type = proxyman.AllocationStrategy_External 104 | default: 105 | return nil, newError("unknown allocation strategy: ", c.Strategy) 106 | } 107 | if c.Concurrency != nil { 108 | config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ 109 | Value: *c.Concurrency, 110 | } 111 | } 112 | 113 | if c.RefreshMin != nil { 114 | config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{ 115 | Value: *c.RefreshMin, 116 | } 117 | } 118 | 119 | return config, nil 120 | } 121 | 122 | type InboundDetourConfig struct { 123 | Protocol string `json:"protocol"` 124 | PortRange *PortRange `json:"port"` 125 | ListenOn *Address `json:"listen"` 126 | Settings *json.RawMessage `json:"settings"` 127 | Tag string `json:"tag"` 128 | Allocation *InboundDetourAllocationConfig `json:"allocate"` 129 | StreamSetting *StreamConfig `json:"streamSettings"` 130 | DomainOverride *StringList `json:"domainOverride"` 131 | SniffingConfig *SniffingConfig `json:"sniffing"` 132 | } 133 | 134 | // Build implements Buildable. 135 | func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { 136 | receiverSettings := &proxyman.ReceiverConfig{} 137 | 138 | if c.PortRange == nil { 139 | return nil, newError("port range not specified in InboundDetour.") 140 | } 141 | receiverSettings.PortRange = c.PortRange.Build() 142 | 143 | if c.ListenOn != nil { 144 | if c.ListenOn.Family().IsDomain() { 145 | return nil, newError("unable to listen on domain address: ", c.ListenOn.Domain()) 146 | } 147 | receiverSettings.Listen = c.ListenOn.Build() 148 | } 149 | if c.Allocation != nil { 150 | concurrency := -1 151 | if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" { 152 | concurrency = int(*c.Allocation.Concurrency) 153 | } 154 | portRange := int(c.PortRange.To - c.PortRange.From + 1) 155 | if concurrency >= 0 && concurrency >= portRange { 156 | return nil, newError("not enough ports. concurrency = ", concurrency, " ports: ", c.PortRange.From, " - ", c.PortRange.To) 157 | } 158 | 159 | as, err := c.Allocation.Build() 160 | if err != nil { 161 | return nil, err 162 | } 163 | receiverSettings.AllocationStrategy = as 164 | } 165 | if c.StreamSetting != nil { 166 | ss, err := c.StreamSetting.Build() 167 | if err != nil { 168 | return nil, err 169 | } 170 | receiverSettings.StreamSettings = ss 171 | } 172 | if c.SniffingConfig != nil { 173 | s, err := c.SniffingConfig.Build() 174 | if err != nil { 175 | return nil, newError("failed to build sniffing config").Base(err) 176 | } 177 | receiverSettings.SniffingSettings = s 178 | } 179 | if c.DomainOverride != nil { 180 | kp, err := toProtocolList(*c.DomainOverride) 181 | if err != nil { 182 | return nil, newError("failed to parse inbound detour config").Base(err) 183 | } 184 | receiverSettings.DomainOverride = kp 185 | } 186 | 187 | settings := []byte("{}") 188 | if c.Settings != nil { 189 | settings = ([]byte)(*c.Settings) 190 | } 191 | rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol) 192 | if err != nil { 193 | return nil, newError("failed to load inbound detour config.").Base(err) 194 | } 195 | if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok { 196 | receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect 197 | } 198 | ts, err := rawConfig.(Buildable).Build() 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | return &core.InboundHandlerConfig{ 204 | Tag: c.Tag, 205 | ReceiverSettings: serial.ToTypedMessage(receiverSettings), 206 | ProxySettings: serial.ToTypedMessage(ts), 207 | }, nil 208 | } 209 | 210 | type OutboundDetourConfig struct { 211 | Protocol string `json:"protocol"` 212 | SendThrough *Address `json:"sendThrough"` 213 | Tag string `json:"tag"` 214 | Settings *json.RawMessage `json:"settings"` 215 | StreamSetting *StreamConfig `json:"streamSettings"` 216 | ProxySettings *ProxyConfig `json:"proxySettings"` 217 | MuxSettings *MuxConfig `json:"mux"` 218 | } 219 | 220 | // Build implements Buildable. 221 | func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { 222 | senderSettings := &proxyman.SenderConfig{} 223 | 224 | if c.SendThrough != nil { 225 | address := c.SendThrough 226 | if address.Family().IsDomain() { 227 | return nil, newError("unable to send through: " + address.String()) 228 | } 229 | senderSettings.Via = address.Build() 230 | } 231 | 232 | if c.StreamSetting != nil { 233 | ss, err := c.StreamSetting.Build() 234 | if err != nil { 235 | return nil, err 236 | } 237 | senderSettings.StreamSettings = ss 238 | } 239 | 240 | if c.ProxySettings != nil { 241 | ps, err := c.ProxySettings.Build() 242 | if err != nil { 243 | return nil, newError("invalid outbound detour proxy settings.").Base(err) 244 | } 245 | senderSettings.ProxySettings = ps 246 | } 247 | 248 | if c.MuxSettings != nil && c.MuxSettings.Enabled { 249 | senderSettings.MultiplexSettings = &proxyman.MultiplexingConfig{ 250 | Enabled: true, 251 | Concurrency: uint32(c.MuxSettings.GetConcurrency()), 252 | } 253 | } 254 | 255 | settings := []byte("{}") 256 | if c.Settings != nil { 257 | settings = ([]byte)(*c.Settings) 258 | } 259 | rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol) 260 | if err != nil { 261 | return nil, newError("failed to parse to outbound detour config.").Base(err) 262 | } 263 | ts, err := rawConfig.(Buildable).Build() 264 | if err != nil { 265 | return nil, err 266 | } 267 | 268 | return &core.OutboundHandlerConfig{ 269 | SenderSettings: serial.ToTypedMessage(senderSettings), 270 | Tag: c.Tag, 271 | ProxySettings: serial.ToTypedMessage(ts), 272 | }, nil 273 | } 274 | 275 | type StatsConfig struct{} 276 | 277 | func (c *StatsConfig) Build() (*stats.Config, error) { 278 | return &stats.Config{}, nil 279 | } 280 | 281 | type Config struct { 282 | Port uint16 `json:"port"` // Port of this Point server. Deprecated. 283 | LogConfig *LogConfig `json:"log"` 284 | RouterConfig *RouterConfig `json:"routing"` 285 | DNSConfig *DnsConfig `json:"dns"` 286 | InboundConfigs []InboundDetourConfig `json:"inbounds"` 287 | OutboundConfigs []OutboundDetourConfig `json:"outbounds"` 288 | InboundConfig *InboundDetourConfig `json:"inbound"` // Deprecated. 289 | OutboundConfig *OutboundDetourConfig `json:"outbound"` // Deprecated. 290 | InboundDetours []InboundDetourConfig `json:"inboundDetour"` // Deprecated. 291 | OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` // Deprecated. 292 | Transport *TransportConfig `json:"transport"` 293 | Policy *PolicyConfig `json:"policy"` 294 | Api *ApiConfig `json:"api"` 295 | Stats *StatsConfig `json:"stats"` 296 | Reverse *ReverseConfig `json:"reverse"` 297 | } 298 | 299 | func applyTransportConfig(s *StreamConfig, t *TransportConfig) { 300 | if s.TCPSettings == nil { 301 | s.TCPSettings = t.TCPConfig 302 | } 303 | if s.KCPSettings == nil { 304 | s.KCPSettings = t.KCPConfig 305 | } 306 | if s.WSSettings == nil { 307 | s.WSSettings = t.WSConfig 308 | } 309 | if s.HTTPSettings == nil { 310 | s.HTTPSettings = t.HTTPConfig 311 | } 312 | if s.DSSettings == nil { 313 | s.DSSettings = t.DSConfig 314 | } 315 | } 316 | 317 | // Build implements Buildable. 318 | func (c *Config) Build() (*core.Config, error) { 319 | config := &core.Config{ 320 | App: []*serial.TypedMessage{ 321 | serial.ToTypedMessage(&dispatcher.Config{}), 322 | serial.ToTypedMessage(&proxyman.InboundConfig{}), 323 | serial.ToTypedMessage(&proxyman.OutboundConfig{}), 324 | }, 325 | } 326 | 327 | if c.Api != nil { 328 | apiConf, err := c.Api.Build() 329 | if err != nil { 330 | return nil, err 331 | } 332 | config.App = append(config.App, serial.ToTypedMessage(apiConf)) 333 | } 334 | 335 | if c.Stats != nil { 336 | statsConf, err := c.Stats.Build() 337 | if err != nil { 338 | return nil, err 339 | } 340 | config.App = append(config.App, serial.ToTypedMessage(statsConf)) 341 | } 342 | 343 | if c.LogConfig != nil { 344 | config.App = append(config.App, serial.ToTypedMessage(c.LogConfig.Build())) 345 | } else { 346 | config.App = append(config.App, serial.ToTypedMessage(DefaultLogConfig())) 347 | } 348 | 349 | if c.RouterConfig != nil { 350 | routerConfig, err := c.RouterConfig.Build() 351 | if err != nil { 352 | return nil, err 353 | } 354 | config.App = append(config.App, serial.ToTypedMessage(routerConfig)) 355 | } 356 | 357 | if c.DNSConfig != nil { 358 | dnsApp, err := c.DNSConfig.Build() 359 | if err != nil { 360 | return nil, newError("failed to parse DNS config").Base(err) 361 | } 362 | config.App = append(config.App, serial.ToTypedMessage(dnsApp)) 363 | } 364 | 365 | if c.Policy != nil { 366 | pc, err := c.Policy.Build() 367 | if err != nil { 368 | return nil, err 369 | } 370 | config.App = append(config.App, serial.ToTypedMessage(pc)) 371 | } 372 | 373 | if c.Reverse != nil { 374 | r, err := c.Reverse.Build() 375 | if err != nil { 376 | return nil, err 377 | } 378 | config.App = append(config.App, serial.ToTypedMessage(r)) 379 | } 380 | 381 | var inbounds []InboundDetourConfig 382 | 383 | if c.InboundConfig != nil { 384 | inbounds = append(inbounds, *c.InboundConfig) 385 | } 386 | 387 | if len(c.InboundDetours) > 0 { 388 | inbounds = append(inbounds, c.InboundDetours...) 389 | } 390 | 391 | if len(c.InboundConfigs) > 0 { 392 | inbounds = append(inbounds, c.InboundConfigs...) 393 | } 394 | 395 | // Backward compatibility. 396 | if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 { 397 | inbounds[0].PortRange = &PortRange{ 398 | From: uint32(c.Port), 399 | To: uint32(c.Port), 400 | } 401 | } 402 | 403 | for _, rawInboundConfig := range inbounds { 404 | if c.Transport != nil { 405 | if rawInboundConfig.StreamSetting == nil { 406 | rawInboundConfig.StreamSetting = &StreamConfig{} 407 | } 408 | applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport) 409 | } 410 | ic, err := rawInboundConfig.Build() 411 | if err != nil { 412 | return nil, err 413 | } 414 | config.Inbound = append(config.Inbound, ic) 415 | } 416 | 417 | var outbounds []OutboundDetourConfig 418 | 419 | if c.OutboundConfig != nil { 420 | outbounds = append(outbounds, *c.OutboundConfig) 421 | } 422 | 423 | if len(c.OutboundDetours) > 0 { 424 | outbounds = append(outbounds, c.OutboundDetours...) 425 | } 426 | 427 | if len(c.OutboundConfigs) > 0 { 428 | outbounds = append(outbounds, c.OutboundConfigs...) 429 | } 430 | 431 | for _, rawOutboundConfig := range outbounds { 432 | if c.Transport != nil { 433 | if rawOutboundConfig.StreamSetting == nil { 434 | rawOutboundConfig.StreamSetting = &StreamConfig{} 435 | } 436 | applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport) 437 | } 438 | oc, err := rawOutboundConfig.Build() 439 | if err != nil { 440 | return nil, err 441 | } 442 | config.Outbound = append(config.Outbound, oc) 443 | } 444 | 445 | return config, nil 446 | } 447 | -------------------------------------------------------------------------------- /tools/conf/transport_internet.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "v2ray.com/core/common/protocol" 9 | "v2ray.com/core/common/serial" 10 | "v2ray.com/core/transport/internet" 11 | "v2ray.com/core/transport/internet/domainsocket" 12 | "v2ray.com/core/transport/internet/http" 13 | "v2ray.com/core/transport/internet/kcp" 14 | "v2ray.com/core/transport/internet/quic" 15 | "v2ray.com/core/transport/internet/tcp" 16 | "v2ray.com/core/transport/internet/tls" 17 | "v2ray.com/core/transport/internet/websocket" 18 | "v2ray.com/ext/sysio" 19 | ) 20 | 21 | var ( 22 | kcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ 23 | "none": func() interface{} { return new(NoOpAuthenticator) }, 24 | "srtp": func() interface{} { return new(SRTPAuthenticator) }, 25 | "utp": func() interface{} { return new(UTPAuthenticator) }, 26 | "wechat-video": func() interface{} { return new(WechatVideoAuthenticator) }, 27 | "dtls": func() interface{} { return new(DTLSAuthenticator) }, 28 | "wireguard": func() interface{} { return new(WireguardAuthenticator) }, 29 | }, "type", "") 30 | 31 | tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ 32 | "none": func() interface{} { return new(NoOpConnectionAuthenticator) }, 33 | "http": func() interface{} { return new(HTTPAuthenticator) }, 34 | }, "type", "") 35 | ) 36 | 37 | type KCPConfig struct { 38 | Mtu *uint32 `json:"mtu"` 39 | Tti *uint32 `json:"tti"` 40 | UpCap *uint32 `json:"uplinkCapacity"` 41 | DownCap *uint32 `json:"downlinkCapacity"` 42 | Congestion *bool `json:"congestion"` 43 | ReadBufferSize *uint32 `json:"readBufferSize"` 44 | WriteBufferSize *uint32 `json:"writeBufferSize"` 45 | HeaderConfig json.RawMessage `json:"header"` 46 | } 47 | 48 | // Build implements Buildable. 49 | func (c *KCPConfig) Build() (proto.Message, error) { 50 | config := new(kcp.Config) 51 | 52 | if c.Mtu != nil { 53 | mtu := *c.Mtu 54 | if mtu < 576 || mtu > 1460 { 55 | return nil, newError("invalid mKCP MTU size: ", mtu).AtError() 56 | } 57 | config.Mtu = &kcp.MTU{Value: mtu} 58 | } 59 | if c.Tti != nil { 60 | tti := *c.Tti 61 | if tti < 10 || tti > 100 { 62 | return nil, newError("invalid mKCP TTI: ", tti).AtError() 63 | } 64 | config.Tti = &kcp.TTI{Value: tti} 65 | } 66 | if c.UpCap != nil { 67 | config.UplinkCapacity = &kcp.UplinkCapacity{Value: *c.UpCap} 68 | } 69 | if c.DownCap != nil { 70 | config.DownlinkCapacity = &kcp.DownlinkCapacity{Value: *c.DownCap} 71 | } 72 | if c.Congestion != nil { 73 | config.Congestion = *c.Congestion 74 | } 75 | if c.ReadBufferSize != nil { 76 | size := *c.ReadBufferSize 77 | if size > 0 { 78 | config.ReadBuffer = &kcp.ReadBuffer{Size: size * 1024 * 1024} 79 | } else { 80 | config.ReadBuffer = &kcp.ReadBuffer{Size: 512 * 1024} 81 | } 82 | } 83 | if c.WriteBufferSize != nil { 84 | size := *c.WriteBufferSize 85 | if size > 0 { 86 | config.WriteBuffer = &kcp.WriteBuffer{Size: size * 1024 * 1024} 87 | } else { 88 | config.WriteBuffer = &kcp.WriteBuffer{Size: 512 * 1024} 89 | } 90 | } 91 | if len(c.HeaderConfig) > 0 { 92 | headerConfig, _, err := kcpHeaderLoader.Load(c.HeaderConfig) 93 | if err != nil { 94 | return nil, newError("invalid mKCP header config.").Base(err).AtError() 95 | } 96 | ts, err := headerConfig.(Buildable).Build() 97 | if err != nil { 98 | return nil, newError("invalid mKCP header config").Base(err).AtError() 99 | } 100 | config.HeaderConfig = serial.ToTypedMessage(ts) 101 | } 102 | 103 | return config, nil 104 | } 105 | 106 | type TCPConfig struct { 107 | HeaderConfig json.RawMessage `json:"header"` 108 | } 109 | 110 | // Build implements Buildable. 111 | func (c *TCPConfig) Build() (proto.Message, error) { 112 | config := new(tcp.Config) 113 | if len(c.HeaderConfig) > 0 { 114 | headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig) 115 | if err != nil { 116 | return nil, newError("invalid TCP header config").Base(err).AtError() 117 | } 118 | ts, err := headerConfig.(Buildable).Build() 119 | if err != nil { 120 | return nil, newError("invalid TCP header config").Base(err).AtError() 121 | } 122 | config.HeaderSettings = serial.ToTypedMessage(ts) 123 | } 124 | 125 | return config, nil 126 | } 127 | 128 | type WebSocketConfig struct { 129 | Path string `json:"path"` 130 | Path2 string `json:"Path"` // The key was misspelled. For backward compatibility, we have to keep track the old key. 131 | Headers map[string]string `json:"headers"` 132 | } 133 | 134 | // Build implements Buildable. 135 | func (c *WebSocketConfig) Build() (proto.Message, error) { 136 | path := c.Path 137 | if len(path) == 0 && len(c.Path2) > 0 { 138 | path = c.Path2 139 | } 140 | header := make([]*websocket.Header, 0, 32) 141 | for key, value := range c.Headers { 142 | header = append(header, &websocket.Header{ 143 | Key: key, 144 | Value: value, 145 | }) 146 | } 147 | 148 | config := &websocket.Config{ 149 | Path: path, 150 | Header: header, 151 | } 152 | return config, nil 153 | } 154 | 155 | type HTTPConfig struct { 156 | Host *StringList `json:"host"` 157 | Path string `json:"path"` 158 | } 159 | 160 | func (c *HTTPConfig) Build() (proto.Message, error) { 161 | config := &http.Config{ 162 | Path: c.Path, 163 | } 164 | if c.Host != nil { 165 | config.Host = []string(*c.Host) 166 | } 167 | return config, nil 168 | } 169 | 170 | type QUICConfig struct { 171 | Header json.RawMessage `json:"header"` 172 | Security string `json:"security"` 173 | Key string `json:"key"` 174 | } 175 | 176 | func (c *QUICConfig) Build() (proto.Message, error) { 177 | config := &quic.Config{ 178 | Key: c.Key, 179 | } 180 | 181 | if len(c.Header) > 0 { 182 | headerConfig, _, err := kcpHeaderLoader.Load(c.Header) 183 | if err != nil { 184 | return nil, newError("invalid QUIC header config.").Base(err).AtError() 185 | } 186 | ts, err := headerConfig.(Buildable).Build() 187 | if err != nil { 188 | return nil, newError("invalid QUIC header config").Base(err).AtError() 189 | } 190 | config.Header = serial.ToTypedMessage(ts) 191 | } 192 | 193 | var st protocol.SecurityType 194 | switch strings.ToLower(c.Security) { 195 | case "aes-128-gcm": 196 | st = protocol.SecurityType_AES128_GCM 197 | case "chacha20-poly1305": 198 | st = protocol.SecurityType_CHACHA20_POLY1305 199 | default: 200 | st = protocol.SecurityType_NONE 201 | } 202 | 203 | config.Security = &protocol.SecurityConfig{ 204 | Type: st, 205 | } 206 | 207 | return config, nil 208 | } 209 | 210 | type DomainSocketConfig struct { 211 | Path string `json:"path"` 212 | Abstract bool `json:"abstract"` 213 | } 214 | 215 | func (c *DomainSocketConfig) Build() (proto.Message, error) { 216 | return &domainsocket.Config{ 217 | Path: c.Path, 218 | Abstract: c.Abstract, 219 | }, nil 220 | } 221 | 222 | type TLSCertConfig struct { 223 | CertFile string `json:"certificateFile"` 224 | CertStr []string `json:"certificate"` 225 | KeyFile string `json:"keyFile"` 226 | KeyStr []string `json:"key"` 227 | Usage string `json:"usage"` 228 | } 229 | 230 | func readFileOrString(f string, s []string) ([]byte, error) { 231 | if len(f) > 0 { 232 | return sysio.ReadFile(f) 233 | } 234 | if len(s) > 0 { 235 | return []byte(strings.Join(s, "\n")), nil 236 | } 237 | return nil, newError("both file and bytes are empty.") 238 | } 239 | 240 | func (c *TLSCertConfig) Build() (*tls.Certificate, error) { 241 | certificate := new(tls.Certificate) 242 | 243 | cert, err := readFileOrString(c.CertFile, c.CertStr) 244 | if err != nil { 245 | return nil, newError("failed to parse certificate").Base(err) 246 | } 247 | certificate.Certificate = cert 248 | 249 | if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 { 250 | key, err := readFileOrString(c.KeyFile, c.KeyStr) 251 | if err != nil { 252 | return nil, newError("failed to parse key").Base(err) 253 | } 254 | certificate.Key = key 255 | } 256 | 257 | switch strings.ToLower(c.Usage) { 258 | case "encipherment": 259 | certificate.Usage = tls.Certificate_ENCIPHERMENT 260 | case "verify": 261 | certificate.Usage = tls.Certificate_AUTHORITY_VERIFY 262 | case "issue": 263 | certificate.Usage = tls.Certificate_AUTHORITY_ISSUE 264 | default: 265 | certificate.Usage = tls.Certificate_ENCIPHERMENT 266 | } 267 | 268 | return certificate, nil 269 | } 270 | 271 | type TLSConfig struct { 272 | Insecure bool `json:"allowInsecure"` 273 | InsecureCiphers bool `json:"allowInsecureCiphers"` 274 | Certs []*TLSCertConfig `json:"certificates"` 275 | ServerName string `json:"serverName"` 276 | ALPN *StringList `json:"alpn"` 277 | } 278 | 279 | // Build implements Buildable. 280 | func (c *TLSConfig) Build() (proto.Message, error) { 281 | config := new(tls.Config) 282 | config.Certificate = make([]*tls.Certificate, len(c.Certs)) 283 | for idx, certConf := range c.Certs { 284 | cert, err := certConf.Build() 285 | if err != nil { 286 | return nil, err 287 | } 288 | config.Certificate[idx] = cert 289 | } 290 | serverName := c.ServerName 291 | config.AllowInsecure = c.Insecure 292 | config.AllowInsecureCiphers = c.InsecureCiphers 293 | if len(c.ServerName) > 0 { 294 | config.ServerName = serverName 295 | } 296 | if c.ALPN != nil && len(*c.ALPN) > 0 { 297 | config.NextProtocol = []string(*c.ALPN) 298 | } 299 | return config, nil 300 | } 301 | 302 | type TransportProtocol string 303 | 304 | // Build implements Buildable. 305 | func (p TransportProtocol) Build() (string, error) { 306 | switch strings.ToLower(string(p)) { 307 | case "tcp": 308 | return "tcp", nil 309 | case "kcp", "mkcp": 310 | return "mkcp", nil 311 | case "ws", "websocket": 312 | return "websocket", nil 313 | case "h2", "http": 314 | return "http", nil 315 | case "ds", "domainsocket": 316 | return "domainsocket", nil 317 | case "quic": 318 | return "quic", nil 319 | default: 320 | return "", newError("Config: unknown transport protocol: ", p) 321 | } 322 | } 323 | 324 | type SocketConfig struct { 325 | Mark int32 `json:"mark"` 326 | TFO *bool `json:"tcpFastOpen"` 327 | TProxy string `json:"tproxy"` 328 | } 329 | 330 | func (c *SocketConfig) Build() (*internet.SocketConfig, error) { 331 | var tfoSettings internet.SocketConfig_TCPFastOpenState 332 | if c.TFO != nil { 333 | if *c.TFO { 334 | tfoSettings = internet.SocketConfig_Enable 335 | } else { 336 | tfoSettings = internet.SocketConfig_Disable 337 | } 338 | } 339 | var tproxy internet.SocketConfig_TProxyMode 340 | switch strings.ToLower(c.TProxy) { 341 | case "tproxy": 342 | tproxy = internet.SocketConfig_TProxy 343 | case "redirect": 344 | tproxy = internet.SocketConfig_Redirect 345 | default: 346 | tproxy = internet.SocketConfig_Off 347 | } 348 | 349 | return &internet.SocketConfig{ 350 | Mark: c.Mark, 351 | Tfo: tfoSettings, 352 | Tproxy: tproxy, 353 | }, nil 354 | } 355 | 356 | type StreamConfig struct { 357 | Network *TransportProtocol `json:"network"` 358 | Security string `json:"security"` 359 | TLSSettings *TLSConfig `json:"tlsSettings"` 360 | TCPSettings *TCPConfig `json:"tcpSettings"` 361 | KCPSettings *KCPConfig `json:"kcpSettings"` 362 | WSSettings *WebSocketConfig `json:"wsSettings"` 363 | HTTPSettings *HTTPConfig `json:"httpSettings"` 364 | DSSettings *DomainSocketConfig `json:"dsSettings"` 365 | QUICSettings *QUICConfig `json:"quicSettings"` 366 | SocketSettings *SocketConfig `json:"sockopt"` 367 | } 368 | 369 | // Build implements Buildable. 370 | func (c *StreamConfig) Build() (*internet.StreamConfig, error) { 371 | config := &internet.StreamConfig{ 372 | ProtocolName: "tcp", 373 | } 374 | if c.Network != nil { 375 | protocol, err := (*c.Network).Build() 376 | if err != nil { 377 | return nil, err 378 | } 379 | config.ProtocolName = protocol 380 | } 381 | if strings.ToLower(c.Security) == "tls" { 382 | tlsSettings := c.TLSSettings 383 | if tlsSettings == nil { 384 | tlsSettings = &TLSConfig{} 385 | } 386 | ts, err := tlsSettings.Build() 387 | if err != nil { 388 | return nil, newError("Failed to build TLS config.").Base(err) 389 | } 390 | tm := serial.ToTypedMessage(ts) 391 | config.SecuritySettings = append(config.SecuritySettings, tm) 392 | config.SecurityType = tm.Type 393 | } 394 | if c.TCPSettings != nil { 395 | ts, err := c.TCPSettings.Build() 396 | if err != nil { 397 | return nil, newError("Failed to build TCP config.").Base(err) 398 | } 399 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 400 | ProtocolName: "tcp", 401 | Settings: serial.ToTypedMessage(ts), 402 | }) 403 | } 404 | if c.KCPSettings != nil { 405 | ts, err := c.KCPSettings.Build() 406 | if err != nil { 407 | return nil, newError("Failed to build mKCP config.").Base(err) 408 | } 409 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 410 | ProtocolName: "mkcp", 411 | Settings: serial.ToTypedMessage(ts), 412 | }) 413 | } 414 | if c.WSSettings != nil { 415 | ts, err := c.WSSettings.Build() 416 | if err != nil { 417 | return nil, newError("Failed to build WebSocket config.").Base(err) 418 | } 419 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 420 | ProtocolName: "websocket", 421 | Settings: serial.ToTypedMessage(ts), 422 | }) 423 | } 424 | if c.HTTPSettings != nil { 425 | ts, err := c.HTTPSettings.Build() 426 | if err != nil { 427 | return nil, newError("Failed to build HTTP config.").Base(err) 428 | } 429 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 430 | ProtocolName: "http", 431 | Settings: serial.ToTypedMessage(ts), 432 | }) 433 | } 434 | if c.DSSettings != nil { 435 | ds, err := c.DSSettings.Build() 436 | if err != nil { 437 | return nil, newError("Failed to build DomainSocket config.").Base(err) 438 | } 439 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 440 | ProtocolName: "domainsocket", 441 | Settings: serial.ToTypedMessage(ds), 442 | }) 443 | } 444 | if c.QUICSettings != nil { 445 | qs, err := c.QUICSettings.Build() 446 | if err != nil { 447 | return nil, newError("failed to build QUIC config").Base(err) 448 | } 449 | config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ 450 | ProtocolName: "quic", 451 | Settings: serial.ToTypedMessage(qs), 452 | }) 453 | } 454 | if c.SocketSettings != nil { 455 | ss, err := c.SocketSettings.Build() 456 | if err != nil { 457 | return nil, newError("failed to build sockopt").Base(err) 458 | } 459 | config.SocketSettings = ss 460 | } 461 | return config, nil 462 | } 463 | 464 | type ProxyConfig struct { 465 | Tag string `json:"tag"` 466 | } 467 | 468 | // Build implements Buildable. 469 | func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) { 470 | if len(v.Tag) == 0 { 471 | return nil, newError("Proxy tag is not set.") 472 | } 473 | return &internet.ProxyConfig{ 474 | Tag: v.Tag, 475 | }, nil 476 | } 477 | --------------------------------------------------------------------------------