mirror of
https://github.com/v2fly/v2ray-core.git
synced 2025-01-02 07:26:24 -05:00
merge ext into core
This commit is contained in:
parent
d84166ba35
commit
4eb2b5e607
@ -10,9 +10,17 @@ import (
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/platform"
|
||||
"v2ray.com/ext/sysio"
|
||||
"v2ray.com/core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
|
||||
}
|
||||
|
||||
func TestGeoIPMatcherContainer(t *testing.T) {
|
||||
container := &router.GeoIPMatcherContainer{}
|
||||
|
||||
@ -112,8 +120,6 @@ func TestGeoIPMatcher(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geoip.dat")))
|
||||
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
@ -126,8 +132,6 @@ func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher6US(t *testing.T) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geoip.dat")))
|
||||
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
@ -140,7 +144,7 @@ func TestGeoIPMatcher6US(t *testing.T) {
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := sysio.ReadAsset("geoip.dat")
|
||||
geoipBytes, err := filesystem.ReadAsset("geoip.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -159,8 +163,6 @@ func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geoip.dat")))
|
||||
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
@ -175,8 +177,6 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geoip.dat")))
|
||||
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
|
@ -15,12 +15,20 @@ import (
|
||||
"v2ray.com/core/common/errors"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/platform"
|
||||
"v2ray.com/core/common/platform/filesystem"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/protocol/http"
|
||||
"v2ray.com/core/common/session"
|
||||
"v2ray.com/ext/sysio"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
|
||||
}
|
||||
|
||||
func withOutbound(outbound *session.Outbound) context.Context {
|
||||
return session.ContextWithOutbound(context.Background(), outbound)
|
||||
}
|
||||
@ -246,7 +254,7 @@ func TestRoutingRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func loadGeoSite(country string) ([]*Domain, error) {
|
||||
geositeBytes, err := sysio.ReadAsset("geosite.dat")
|
||||
geositeBytes, err := filesystem.ReadAsset("geosite.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -265,8 +273,6 @@ func loadGeoSite(country string) ([]*Domain, error) {
|
||||
}
|
||||
|
||||
func TestChinaSites(t *testing.T) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geosite.dat")))
|
||||
|
||||
domains, err := loadGeoSite("CN")
|
||||
common.Must(err)
|
||||
|
||||
@ -309,8 +315,6 @@ func TestChinaSites(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
common.Must(sysio.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", "release", "config", "geoip.dat")))
|
||||
|
||||
var geoips []*GeoIP
|
||||
|
||||
{
|
||||
|
44
common/platform/filesystem/file.go
Normal file
44
common/platform/filesystem/file.go
Normal file
@ -0,0 +1,44 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"v2ray.com/core/common/buf"
|
||||
"v2ray.com/core/common/platform"
|
||||
)
|
||||
|
||||
type FileReaderFunc func(path string) (io.ReadCloser, error)
|
||||
|
||||
var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
reader, err := NewFileReader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return buf.ReadAllToBytes(reader)
|
||||
}
|
||||
|
||||
func ReadAsset(file string) ([]byte, error) {
|
||||
return ReadFile(platform.GetAssetLocation(file))
|
||||
}
|
||||
|
||||
func CopyFile(dst string, src string) error {
|
||||
bytes, err := ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(bytes)
|
||||
return err
|
||||
}
|
5
infra/bazel/BUILD
Normal file
5
infra/bazel/BUILD
Normal file
@ -0,0 +1,5 @@
|
||||
filegroup(
|
||||
name = "rules",
|
||||
srcs = glob(["*.bzl"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
66
infra/bazel/build.bzl
Normal file
66
infra/bazel/build.bzl
Normal file
@ -0,0 +1,66 @@
|
||||
def _go_command(ctx):
|
||||
output = ctx.attr.output
|
||||
if ctx.attr.os == "windows":
|
||||
output = output + ".exe"
|
||||
|
||||
output_file = ctx.actions.declare_file(ctx.attr.os + "/" + ctx.attr.arch + "/" + output)
|
||||
pkg = ctx.attr.pkg
|
||||
|
||||
ld_flags = "-s -w"
|
||||
if ctx.attr.ld:
|
||||
ld_flags = ld_flags + " " + ctx.attr.ld
|
||||
|
||||
options = [
|
||||
"go",
|
||||
"build",
|
||||
"-o", output_file.path,
|
||||
"-compiler", "gc",
|
||||
"-gcflags", '"all=-trimpath=${GOPATH}/src"',
|
||||
"-asmflags", '"all=-trimpath=${GOPATH}/src"',
|
||||
"-ldflags", "'%s'" % ld_flags,
|
||||
"-tags", "'%s'" % ctx.attr.gotags,
|
||||
pkg,
|
||||
]
|
||||
|
||||
command = " ".join(options)
|
||||
|
||||
envs = [
|
||||
"CGO_ENABLED=0",
|
||||
"GOOS="+ctx.attr.os,
|
||||
"GOARCH="+ctx.attr.arch,
|
||||
"GOROOT_FINAL=/go"
|
||||
]
|
||||
|
||||
if ctx.attr.mips: # https://github.com/golang/go/issues/27260
|
||||
envs+=["GOMIPS="+ctx.attr.mips]
|
||||
envs+=["GOMIPS64="+ctx.attr.mips]
|
||||
envs+=["GOMIPSLE="+ctx.attr.mips]
|
||||
envs+=["GOMIPS64LE="+ctx.attr.mips]
|
||||
if ctx.attr.arm:
|
||||
envs+=["GOARM="+ctx.attr.arm]
|
||||
|
||||
command = " ".join(envs) + " " + command
|
||||
|
||||
ctx.actions.run_shell(
|
||||
outputs = [output_file],
|
||||
command = command,
|
||||
use_default_shell_env = True,
|
||||
)
|
||||
runfiles = ctx.runfiles(files = [output_file])
|
||||
return [DefaultInfo(executable = output_file, runfiles = runfiles)]
|
||||
|
||||
|
||||
foreign_go_binary = rule(
|
||||
_go_command,
|
||||
attrs = {
|
||||
'pkg': attr.string(),
|
||||
'output': attr.string(),
|
||||
'os': attr.string(mandatory=True),
|
||||
'arch': attr.string(mandatory=True),
|
||||
'mips': attr.string(),
|
||||
'arm': attr.string(),
|
||||
'ld': attr.string(),
|
||||
'gotags': attr.string(),
|
||||
},
|
||||
executable = True,
|
||||
)
|
23
infra/bazel/gpg.bzl
Normal file
23
infra/bazel/gpg.bzl
Normal file
@ -0,0 +1,23 @@
|
||||
def _gpg_sign_impl(ctx):
|
||||
output_file = ctx.actions.declare_file(ctx.file.base.basename + ctx.attr.suffix, sibling = ctx.file.base)
|
||||
if not ctx.configuration.default_shell_env.get("GPG_PASS"):
|
||||
ctx.actions.write(output_file, "")
|
||||
else:
|
||||
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)
|
||||
ctx.actions.run_shell(
|
||||
command = command,
|
||||
use_default_shell_env = True,
|
||||
inputs = [ctx.file.base],
|
||||
outputs = [output_file],
|
||||
progress_message = "Signing binary",
|
||||
mnemonic = "gpg",
|
||||
)
|
||||
return [DefaultInfo(files = depset([output_file]))]
|
||||
|
||||
gpg_sign = rule(
|
||||
implementation = _gpg_sign_impl,
|
||||
attrs = {
|
||||
"base": attr.label(allow_single_file=True),
|
||||
"suffix": attr.string(default=".sig"),
|
||||
},
|
||||
)
|
21
infra/bazel/matrix.bzl
Normal file
21
infra/bazel/matrix.bzl
Normal file
@ -0,0 +1,21 @@
|
||||
SUPPORTED_MATRIX = [
|
||||
("windows", "amd64"),
|
||||
("windows", "386"),
|
||||
("darwin", "amd64"),
|
||||
("linux", "amd64"),
|
||||
("linux", "386"),
|
||||
("linux", "arm64"),
|
||||
("linux", "arm"),
|
||||
("linux", "mips64"),
|
||||
("linux", "mips"),
|
||||
("linux", "mips64le"),
|
||||
("linux", "mipsle"),
|
||||
("linux", "ppc64"),
|
||||
("linux", "ppc64le"),
|
||||
("linux", "s390x"),
|
||||
("freebsd", "amd64"),
|
||||
("freebsd", "386"),
|
||||
("openbsd", "amd64"),
|
||||
("openbsd", "386"),
|
||||
("dragonfly", "amd64"),
|
||||
]
|
164
infra/bazel/zip.bzl
Normal file
164
infra/bazel/zip.bzl
Normal file
@ -0,0 +1,164 @@
|
||||
# Copied from google/nomulus project as we don't want to import the whole repository.
|
||||
|
||||
ZIPPER = "@bazel_tools//tools/zip:zipper"
|
||||
|
||||
def long_path(ctx, file_):
|
||||
"""Constructs canonical runfile path relative to TEST_SRCDIR.
|
||||
Args:
|
||||
ctx: A Skylark rule context.
|
||||
file_: A File object that should appear in the runfiles for the test.
|
||||
Returns:
|
||||
A string path relative to TEST_SRCDIR suitable for use in tests and
|
||||
testing infrastructure.
|
||||
"""
|
||||
if file_.short_path.startswith("../"):
|
||||
return file_.short_path[3:]
|
||||
if file_.owner and file_.owner.workspace_root:
|
||||
return file_.owner.workspace_root + "/" + file_.short_path
|
||||
return ctx.workspace_name + "/" + file_.short_path
|
||||
|
||||
def collect_runfiles(targets):
|
||||
"""Aggregates runfiles from targets.
|
||||
Args:
|
||||
targets: A list of Bazel targets.
|
||||
Returns:
|
||||
A list of Bazel files.
|
||||
"""
|
||||
data = depset()
|
||||
for target in targets:
|
||||
if hasattr(target, "runfiles"):
|
||||
data += target.runfiles.files
|
||||
continue
|
||||
if hasattr(target, "data_runfiles"):
|
||||
data += target.data_runfiles.files
|
||||
if hasattr(target, "default_runfiles"):
|
||||
data += target.default_runfiles.files
|
||||
return data
|
||||
|
||||
def _get_runfiles(target, attribute):
|
||||
runfiles = getattr(target, attribute, None)
|
||||
if runfiles:
|
||||
return runfiles.files
|
||||
return []
|
||||
|
||||
def _zip_file(ctx):
|
||||
"""Implementation of zip_file() rule."""
|
||||
for s, d in ctx.attr.mappings.items():
|
||||
if (s.startswith("/") or s.endswith("/") or
|
||||
d.startswith("/") or d.endswith("/")):
|
||||
fail("mappings should not begin or end with slash")
|
||||
srcs = depset()
|
||||
srcs += ctx.files.srcs
|
||||
srcs += ctx.files.data
|
||||
srcs += collect_runfiles(ctx.attr.data)
|
||||
mapped = _map_sources(ctx, srcs, ctx.attr.mappings)
|
||||
cmd = [
|
||||
"#!/bin/sh",
|
||||
"set -e",
|
||||
'repo="$(pwd)"',
|
||||
'zipper="${repo}/%s"' % ctx.file._zipper.path,
|
||||
'archive="${repo}/%s"' % ctx.outputs.out.path,
|
||||
'tmp="$(mktemp -d "${TMPDIR:-/tmp}/zip_file.XXXXXXXXXX")"',
|
||||
'cd "${tmp}"',
|
||||
]
|
||||
cmd += [
|
||||
'"${zipper}" x "${repo}/%s"' % dep.zip_file.path
|
||||
for dep in ctx.attr.deps
|
||||
]
|
||||
cmd += ["rm %s" % filename for filename in ctx.attr.exclude]
|
||||
cmd += [
|
||||
'mkdir -p "${tmp}/%s"' % zip_path
|
||||
for zip_path in depset(
|
||||
[
|
||||
zip_path[:zip_path.rindex("/")]
|
||||
for _, zip_path in mapped
|
||||
if "/" in zip_path
|
||||
],
|
||||
)
|
||||
]
|
||||
cmd += [
|
||||
'ln -sf "${repo}/%s" "${tmp}/%s"' % (path, zip_path)
|
||||
for path, zip_path in mapped
|
||||
]
|
||||
cmd += [
|
||||
("find . | sed 1d | cut -c 3- | LC_ALL=C sort" +
|
||||
' | xargs "${zipper}" cC "${archive}"'),
|
||||
'cd "${repo}"',
|
||||
'rm -rf "${tmp}"',
|
||||
]
|
||||
script = ctx.new_file(ctx.bin_dir, "%s.sh" % ctx.label.name)
|
||||
ctx.file_action(output = script, content = "\n".join(cmd), executable = True)
|
||||
inputs = [ctx.file._zipper]
|
||||
inputs += [dep.zip_file for dep in ctx.attr.deps]
|
||||
inputs += list(srcs)
|
||||
ctx.action(
|
||||
inputs = inputs,
|
||||
outputs = [ctx.outputs.out],
|
||||
executable = script,
|
||||
mnemonic = "zip",
|
||||
progress_message = "Creating zip with %d inputs %s" % (
|
||||
len(inputs),
|
||||
ctx.label,
|
||||
),
|
||||
)
|
||||
return struct(files = depset([ctx.outputs.out]), zip_file = ctx.outputs.out)
|
||||
|
||||
def _map_sources(ctx, srcs, mappings):
|
||||
"""Calculates paths in zip file for srcs."""
|
||||
|
||||
# order mappings with more path components first
|
||||
mappings = sorted([
|
||||
(-len(source.split("/")), source, dest)
|
||||
for source, dest in mappings.items()
|
||||
])
|
||||
|
||||
# get rid of the integer part of tuple used for sorting
|
||||
mappings = [(source, dest) for _, source, dest in mappings]
|
||||
mappings_indexes = range(len(mappings))
|
||||
used = {i: False for i in mappings_indexes}
|
||||
mapped = []
|
||||
for file_ in srcs:
|
||||
run_path = long_path(ctx, file_)
|
||||
zip_path = None
|
||||
for i in mappings_indexes:
|
||||
source = mappings[i][0]
|
||||
dest = mappings[i][1]
|
||||
if not source:
|
||||
if dest:
|
||||
zip_path = dest + "/" + run_path
|
||||
else:
|
||||
zip_path = run_path
|
||||
elif source == run_path:
|
||||
if dest:
|
||||
zip_path = dest
|
||||
else:
|
||||
zip_path = run_path
|
||||
elif run_path.startswith(source + "/"):
|
||||
if dest:
|
||||
zip_path = dest + run_path[len(source):]
|
||||
else:
|
||||
zip_path = run_path[len(source) + 1:]
|
||||
else:
|
||||
continue
|
||||
used[i] = True
|
||||
break
|
||||
if not zip_path:
|
||||
fail("no mapping matched: " + run_path)
|
||||
mapped += [(file_.path, zip_path)]
|
||||
for i in mappings_indexes:
|
||||
if not used[i]:
|
||||
fail('superfluous mapping: "%s" -> "%s"' % mappings[i])
|
||||
return mapped
|
||||
|
||||
pkg_zip = rule(
|
||||
implementation = _zip_file,
|
||||
attrs = {
|
||||
"out": attr.output(mandatory = True),
|
||||
"srcs": attr.label_list(allow_files = True),
|
||||
"data": attr.label_list(allow_files = True),
|
||||
"deps": attr.label_list(providers = ["zip_file"]),
|
||||
"exclude": attr.string_list(),
|
||||
"mappings": attr.string_dict(),
|
||||
"_zipper": attr.label(default = Label(ZIPPER), single_file = True),
|
||||
},
|
||||
)
|
39
infra/conf/api.go
Normal file
39
infra/conf/api.go
Normal file
@ -0,0 +1,39 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/app/commander"
|
||||
loggerservice "v2ray.com/core/app/log/command"
|
||||
handlerservice "v2ray.com/core/app/proxyman/command"
|
||||
statsservice "v2ray.com/core/app/stats/command"
|
||||
"v2ray.com/core/common/serial"
|
||||
)
|
||||
|
||||
type ApiConfig struct {
|
||||
Tag string `json:"tag"`
|
||||
Services []string `json:"services"`
|
||||
}
|
||||
|
||||
func (c *ApiConfig) Build() (*commander.Config, error) {
|
||||
if len(c.Tag) == 0 {
|
||||
return nil, newError("Api tag can't be empty.")
|
||||
}
|
||||
|
||||
services := make([]*serial.TypedMessage, 0, 16)
|
||||
for _, s := range c.Services {
|
||||
switch strings.ToLower(s) {
|
||||
case "handlerservice":
|
||||
services = append(services, serial.ToTypedMessage(&handlerservice.Config{}))
|
||||
case "loggerservice":
|
||||
services = append(services, serial.ToTypedMessage(&loggerservice.Config{}))
|
||||
case "statsservice":
|
||||
services = append(services, serial.ToTypedMessage(&statsservice.Config{}))
|
||||
}
|
||||
}
|
||||
|
||||
return &commander.Config{
|
||||
Tag: c.Tag,
|
||||
Service: services,
|
||||
}, nil
|
||||
}
|
53
infra/conf/blackhole.go
Normal file
53
infra/conf/blackhole.go
Normal file
@ -0,0 +1,53 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/proxy/blackhole"
|
||||
)
|
||||
|
||||
type NoneResponse struct{}
|
||||
|
||||
func (*NoneResponse) Build() (proto.Message, error) {
|
||||
return new(blackhole.NoneResponse), nil
|
||||
}
|
||||
|
||||
type HttpResponse struct{}
|
||||
|
||||
func (*HttpResponse) Build() (proto.Message, error) {
|
||||
return new(blackhole.HTTPResponse), nil
|
||||
}
|
||||
|
||||
type BlackholeConfig struct {
|
||||
Response json.RawMessage `json:"response"`
|
||||
}
|
||||
|
||||
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
||||
config := new(blackhole.Config)
|
||||
if v.Response != nil {
|
||||
response, _, err := configLoader.Load(v.Response)
|
||||
if err != nil {
|
||||
return nil, newError("Config: Failed to parse Blackhole response config.").Base(err)
|
||||
}
|
||||
responseSettings, err := response.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Response = serial.ToTypedMessage(responseSettings)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
var (
|
||||
configLoader = NewJSONConfigLoader(
|
||||
ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoneResponse) },
|
||||
"http": func() interface{} { return new(HttpResponse) },
|
||||
},
|
||||
"type",
|
||||
"")
|
||||
)
|
34
infra/conf/blackhole_test.go
Normal file
34
infra/conf/blackhole_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/blackhole"
|
||||
)
|
||||
|
||||
func TestHTTPResponseJSON(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(BlackholeConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"response": {
|
||||
"type": "http"
|
||||
}
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &blackhole.Config{
|
||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: `{}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &blackhole.Config{},
|
||||
},
|
||||
})
|
||||
}
|
7
infra/conf/buildable.go
Normal file
7
infra/conf/buildable.go
Normal file
@ -0,0 +1,7 @@
|
||||
package conf
|
||||
|
||||
import "github.com/golang/protobuf/proto"
|
||||
|
||||
type Buildable interface {
|
||||
Build() (proto.Message, error)
|
||||
}
|
48
infra/conf/command/command.go
Normal file
48
infra/conf/command/command.go
Normal file
@ -0,0 +1,48 @@
|
||||
package command
|
||||
|
||||
//go:generate errorgen
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/infra/conf/serial"
|
||||
"v2ray.com/core/infra/control"
|
||||
)
|
||||
|
||||
type ConfigCommand struct{}
|
||||
|
||||
func (c *ConfigCommand) Name() string {
|
||||
return "config"
|
||||
}
|
||||
|
||||
func (c *ConfigCommand) Description() control.Description {
|
||||
return control.Description{
|
||||
Short: "Convert config among different formats.",
|
||||
Usage: []string{
|
||||
"v2ctl config",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConfigCommand) Execute(args []string) error {
|
||||
pbConfig, err := serial.LoadJSONConfig(os.Stdin)
|
||||
if err != nil {
|
||||
return newError("failed to parse json config").Base(err)
|
||||
}
|
||||
|
||||
bytesConfig, err := proto.Marshal(pbConfig)
|
||||
if err != nil {
|
||||
return newError("failed to marshal proto config").Base(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(bytesConfig); err != nil {
|
||||
return newError("failed to write proto config").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(control.RegisterCommand(&ConfigCommand{}))
|
||||
}
|
9
infra/conf/command/errors.generated.go
Normal file
9
infra/conf/command/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package command
|
||||
|
||||
import "v2ray.com/core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
189
infra/conf/common.go
Normal file
189
infra/conf/common.go
Normal file
@ -0,0 +1,189 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
)
|
||||
|
||||
type StringList []string
|
||||
|
||||
func NewStringList(raw []string) *StringList {
|
||||
list := StringList(raw)
|
||||
return &list
|
||||
}
|
||||
|
||||
func (v StringList) Len() int {
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (v *StringList) UnmarshalJSON(data []byte) error {
|
||||
var strarray []string
|
||||
if err := json.Unmarshal(data, &strarray); err == nil {
|
||||
*v = *NewStringList(strarray)
|
||||
return nil
|
||||
}
|
||||
|
||||
var rawstr string
|
||||
if err := json.Unmarshal(data, &rawstr); err == nil {
|
||||
strlist := strings.Split(rawstr, ",")
|
||||
*v = *NewStringList(strlist)
|
||||
return nil
|
||||
}
|
||||
return newError("unknown format of a string list: " + string(data))
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
net.Address
|
||||
}
|
||||
|
||||
func (v *Address) UnmarshalJSON(data []byte) error {
|
||||
var rawStr string
|
||||
if err := json.Unmarshal(data, &rawStr); err != nil {
|
||||
return newError("invalid address: ", string(data)).Base(err)
|
||||
}
|
||||
v.Address = net.ParseAddress(rawStr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Address) Build() *net.IPOrDomain {
|
||||
return net.NewIPOrDomain(v.Address)
|
||||
}
|
||||
|
||||
type Network string
|
||||
|
||||
func (v Network) Build() net.Network {
|
||||
switch strings.ToLower(string(v)) {
|
||||
case "tcp":
|
||||
return net.Network_TCP
|
||||
case "udp":
|
||||
return net.Network_UDP
|
||||
default:
|
||||
return net.Network_Unknown
|
||||
}
|
||||
}
|
||||
|
||||
type NetworkList []Network
|
||||
|
||||
func (v *NetworkList) UnmarshalJSON(data []byte) error {
|
||||
var strarray []Network
|
||||
if err := json.Unmarshal(data, &strarray); err == nil {
|
||||
nl := NetworkList(strarray)
|
||||
*v = nl
|
||||
return nil
|
||||
}
|
||||
|
||||
var rawstr Network
|
||||
if err := json.Unmarshal(data, &rawstr); err == nil {
|
||||
strlist := strings.Split(string(rawstr), ",")
|
||||
nl := make([]Network, len(strlist))
|
||||
for idx, network := range strlist {
|
||||
nl[idx] = Network(network)
|
||||
}
|
||||
*v = nl
|
||||
return nil
|
||||
}
|
||||
return newError("unknown format of a string list: " + string(data))
|
||||
}
|
||||
|
||||
func (v *NetworkList) Build() []net.Network {
|
||||
if v == nil {
|
||||
return []net.Network{net.Network_TCP}
|
||||
}
|
||||
|
||||
list := make([]net.Network, 0, len(*v))
|
||||
for _, network := range *v {
|
||||
list = append(list, network.Build())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func parseIntPort(data []byte) (net.Port, error) {
|
||||
var intPort uint32
|
||||
err := json.Unmarshal(data, &intPort)
|
||||
if err != nil {
|
||||
return net.Port(0), err
|
||||
}
|
||||
return net.PortFromInt(intPort)
|
||||
}
|
||||
|
||||
func parseStringPort(data []byte) (net.Port, net.Port, error) {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return net.Port(0), net.Port(0), err
|
||||
}
|
||||
if strings.HasPrefix(s, "env:") {
|
||||
s = s[4:]
|
||||
s = os.Getenv(s)
|
||||
}
|
||||
|
||||
pair := strings.SplitN(s, "-", 2)
|
||||
if len(pair) == 0 {
|
||||
return net.Port(0), net.Port(0), newError("Config: Invalid port range: ", s)
|
||||
}
|
||||
if len(pair) == 1 {
|
||||
port, err := net.PortFromString(pair[0])
|
||||
return port, port, err
|
||||
}
|
||||
|
||||
fromPort, err := net.PortFromString(pair[0])
|
||||
if err != nil {
|
||||
return net.Port(0), net.Port(0), err
|
||||
}
|
||||
toPort, err := net.PortFromString(pair[1])
|
||||
if err != nil {
|
||||
return net.Port(0), net.Port(0), err
|
||||
}
|
||||
return fromPort, toPort, nil
|
||||
}
|
||||
|
||||
type PortRange struct {
|
||||
From uint32
|
||||
To uint32
|
||||
}
|
||||
|
||||
func (v *PortRange) Build() *net.PortRange {
|
||||
return &net.PortRange{
|
||||
From: v.From,
|
||||
To: v.To,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||
func (v *PortRange) UnmarshalJSON(data []byte) error {
|
||||
port, err := parseIntPort(data)
|
||||
if err == nil {
|
||||
v.From = uint32(port)
|
||||
v.To = uint32(port)
|
||||
return nil
|
||||
}
|
||||
|
||||
from, to, err := parseStringPort(data)
|
||||
if err == nil {
|
||||
v.From = uint32(from)
|
||||
v.To = uint32(to)
|
||||
if v.From > v.To {
|
||||
return newError("invalid port range ", v.From, " -> ", v.To)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return newError("invalid port range: ", string(data))
|
||||
}
|
||||
|
||||
type User struct {
|
||||
EmailString string `json:"email"`
|
||||
LevelByte byte `json:"level"`
|
||||
}
|
||||
|
||||
func (v *User) Build() *protocol.User {
|
||||
return &protocol.User{
|
||||
Email: v.EmailString,
|
||||
Level: uint32(v.LevelByte),
|
||||
}
|
||||
}
|
211
infra/conf/common_test.go
Normal file
211
infra/conf/common_test.go
Normal file
@ -0,0 +1,211 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"v2ray.com/core/common/protocol"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/net"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func TestStringListUnmarshalError(t *testing.T) {
|
||||
rawJson := `1234`
|
||||
list := new(StringList)
|
||||
err := json.Unmarshal([]byte(rawJson), list)
|
||||
if err == nil {
|
||||
t.Error("expected error, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringListLen(t *testing.T) {
|
||||
rawJson := `"a, b, c, d"`
|
||||
var list StringList
|
||||
err := json.Unmarshal([]byte(rawJson), &list)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPParsing(t *testing.T) {
|
||||
rawJson := "\"8.8.8.8\""
|
||||
var address Address
|
||||
err := json.Unmarshal([]byte(rawJson), &address)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainParsing(t *testing.T) {
|
||||
rawJson := "\"v2ray.com\""
|
||||
var address Address
|
||||
common.Must(json.Unmarshal([]byte(rawJson), &address))
|
||||
if address.Domain() != "v2ray.com" {
|
||||
t.Error("domain: ", address.Domain())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidAddressJson(t *testing.T) {
|
||||
rawJson := "1234"
|
||||
var address Address
|
||||
err := json.Unmarshal([]byte(rawJson), &address)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNetwork(t *testing.T) {
|
||||
var network Network
|
||||
common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
|
||||
if v := network.Build(); v != net.Network_TCP {
|
||||
t.Error("network: ", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayNetworkList(t *testing.T) {
|
||||
var list NetworkList
|
||||
common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
|
||||
|
||||
nlist := list.Build()
|
||||
if !net.HasNetwork(nlist, net.Network_TCP) {
|
||||
t.Error("no tcp network")
|
||||
}
|
||||
if net.HasNetwork(nlist, net.Network_UDP) {
|
||||
t.Error("has udp network")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNetworkList(t *testing.T) {
|
||||
var list NetworkList
|
||||
common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
|
||||
|
||||
nlist := list.Build()
|
||||
if !net.HasNetwork(nlist, net.Network_TCP) {
|
||||
t.Error("no tcp network")
|
||||
}
|
||||
if net.HasNetwork(nlist, net.Network_UDP) {
|
||||
t.Error("has udp network")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidNetworkJson(t *testing.T) {
|
||||
var list NetworkList
|
||||
err := json.Unmarshal([]byte("0"), &list)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
common.Must(json.Unmarshal([]byte("1234"), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverRangeIntPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
err := json.Unmarshal([]byte("70000"), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte("-1"), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvPort(t *testing.T) {
|
||||
common.Must(os.Setenv("PORT", "1234"))
|
||||
|
||||
var portRange PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleStringPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"1234\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
From: 1234, To: 1234,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringPairPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange))
|
||||
|
||||
if r := cmp.Diff(portRange, PortRange{
|
||||
From: 1234, To: 5678,
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverRangeStringPort(t *testing.T) {
|
||||
var portRange PortRange
|
||||
err := json.Unmarshal([]byte("\"65536\""), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte("\"70000-80000\""), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte("\"1-90000\""), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte("\"700-600\""), &portRange)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserParsing(t *testing.T) {
|
||||
user := new(User)
|
||||
common.Must(json.Unmarshal([]byte(`{
|
||||
"id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
|
||||
"email": "love@v2ray.com",
|
||||
"level": 1,
|
||||
"alterId": 100
|
||||
}`), user))
|
||||
|
||||
nUser := user.Build()
|
||||
if r := cmp.Diff(nUser, &protocol.User{
|
||||
Level: 1,
|
||||
Email: "love@v2ray.com",
|
||||
}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidUserJson(t *testing.T) {
|
||||
user := new(User)
|
||||
err := json.Unmarshal([]byte(`{"email": 1234}`), user)
|
||||
if err == nil {
|
||||
t.Error("nil error")
|
||||
}
|
||||
}
|
3
infra/conf/conf.go
Normal file
3
infra/conf/conf.go
Normal file
@ -0,0 +1,3 @@
|
||||
package conf
|
||||
|
||||
//go:generate errorgen
|
176
infra/conf/dns.go
Normal file
176
infra/conf/dns.go
Normal file
@ -0,0 +1,176 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/app/dns"
|
||||
"v2ray.com/core/app/router"
|
||||
"v2ray.com/core/common/net"
|
||||
)
|
||||
|
||||
type NameServerConfig struct {
|
||||
Address *Address
|
||||
Port uint16
|
||||
Domains []string
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||
var address Address
|
||||
if err := json.Unmarshal(data, &address); err == nil {
|
||||
c.Address = &address
|
||||
c.Port = 53
|
||||
return nil
|
||||
}
|
||||
|
||||
var advanced struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Domains []string `json:"domains"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||
c.Address = advanced.Address
|
||||
c.Port = advanced.Port
|
||||
c.Domains = advanced.Domains
|
||||
return nil
|
||||
}
|
||||
|
||||
return newError("failed to parse name server: ", string(data))
|
||||
}
|
||||
|
||||
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
|
||||
switch t {
|
||||
case router.Domain_Domain:
|
||||
return dns.DomainMatchingType_Subdomain
|
||||
case router.Domain_Full:
|
||||
return dns.DomainMatchingType_Full
|
||||
case router.Domain_Plain:
|
||||
return dns.DomainMatchingType_Keyword
|
||||
case router.Domain_Regex:
|
||||
return dns.DomainMatchingType_Regex
|
||||
default:
|
||||
panic("unknown domain type")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
||||
if c.Address == nil {
|
||||
return nil, newError("NameServer address is not specified.")
|
||||
}
|
||||
|
||||
var domains []*dns.NameServer_PriorityDomain
|
||||
|
||||
for _, d := range c.Domains {
|
||||
parsedDomain, err := parseDomainRule(d)
|
||||
if err != nil {
|
||||
return nil, newError("invalid domain rule: ", d).Base(err)
|
||||
}
|
||||
|
||||
for _, pd := range parsedDomain {
|
||||
domains = append(domains, &dns.NameServer_PriorityDomain{
|
||||
Type: toDomainMatchingType(pd.Type),
|
||||
Domain: pd.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &dns.NameServer{
|
||||
Address: &net.Endpoint{
|
||||
Network: net.Network_UDP,
|
||||
Address: c.Address.Build(),
|
||||
Port: uint32(c.Port),
|
||||
},
|
||||
PrioritizedDomain: domains,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
|
||||
router.Domain_Full: dns.DomainMatchingType_Full,
|
||||
router.Domain_Domain: dns.DomainMatchingType_Subdomain,
|
||||
router.Domain_Plain: dns.DomainMatchingType_Keyword,
|
||||
router.Domain_Regex: dns.DomainMatchingType_Regex,
|
||||
}
|
||||
|
||||
// DnsConfig is a JSON serializable object for dns.Config.
|
||||
type DnsConfig struct {
|
||||
Servers []*NameServerConfig `json:"servers"`
|
||||
Hosts map[string]*Address `json:"hosts"`
|
||||
ClientIP *Address `json:"clientIp"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
func getHostMapping(addr *Address) *dns.Config_HostMapping {
|
||||
if addr.Family().IsIP() {
|
||||
return &dns.Config_HostMapping{
|
||||
Ip: [][]byte{[]byte(addr.IP())},
|
||||
}
|
||||
} else {
|
||||
return &dns.Config_HostMapping{
|
||||
ProxiedDomain: addr.Domain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *DnsConfig) Build() (*dns.Config, error) {
|
||||
config := &dns.Config{
|
||||
Tag: c.Tag,
|
||||
}
|
||||
|
||||
if c.ClientIP != nil {
|
||||
if !c.ClientIP.Family().IsIP() {
|
||||
return nil, newError("not an IP address:", c.ClientIP.String())
|
||||
}
|
||||
config.ClientIp = []byte(c.ClientIP.IP())
|
||||
}
|
||||
|
||||
for _, server := range c.Servers {
|
||||
ns, err := server.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build name server").Base(err)
|
||||
}
|
||||
config.NameServer = append(config.NameServer, ns)
|
||||
}
|
||||
|
||||
if c.Hosts != nil && len(c.Hosts) > 0 {
|
||||
domains := make([]string, 0, len(c.Hosts))
|
||||
for domain := range c.Hosts {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
for _, domain := range domains {
|
||||
addr := c.Hosts[domain]
|
||||
var mappings []*dns.Config_HostMapping
|
||||
if strings.HasPrefix(domain, "domain:") {
|
||||
mapping := getHostMapping(addr)
|
||||
mapping.Type = dns.DomainMatchingType_Subdomain
|
||||
mapping.Domain = domain[7:]
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
} else if strings.HasPrefix(domain, "geosite:") {
|
||||
domains, err := loadGeositeWithAttr("geosite.dat", strings.ToUpper(domain[8:]))
|
||||
if err != nil {
|
||||
return nil, newError("invalid geosite settings: ", domain).Base(err)
|
||||
}
|
||||
for _, d := range domains {
|
||||
mapping := getHostMapping(addr)
|
||||
mapping.Type = typeMap[d.Type]
|
||||
mapping.Domain = d.Value
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
} else {
|
||||
mapping := getHostMapping(addr)
|
||||
mapping.Type = dns.DomainMatchingType_Full
|
||||
mapping.Domain = domain
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
|
||||
config.StaticHosts = append(config.StaticHosts, mappings...)
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
12
infra/conf/dns_proxy.go
Normal file
12
infra/conf/dns_proxy.go
Normal file
@ -0,0 +1,12 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/proxy/dns"
|
||||
)
|
||||
|
||||
type DnsOutboundConfig struct{}
|
||||
|
||||
func (c *DnsOutboundConfig) Build() (proto.Message, error) {
|
||||
return new(dns.Config), nil
|
||||
}
|
103
infra/conf/dns_test.go
Normal file
103
infra/conf/dns_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/app/dns"
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/platform"
|
||||
"v2ray.com/core/common/platform/filesystem"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
common.Must(err)
|
||||
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
|
||||
common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
|
||||
}
|
||||
func TestDnsConfigParsing(t *testing.T) {
|
||||
geositePath := platform.GetAssetLocation("geosite.dat")
|
||||
defer func() {
|
||||
os.Remove(geositePath)
|
||||
}()
|
||||
|
||||
parserCreator := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
config := new(DnsConfig)
|
||||
if err := json.Unmarshal([]byte(s), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Build()
|
||||
}
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"servers": [{
|
||||
"address": "8.8.8.8",
|
||||
"port": 5353,
|
||||
"domains": ["domain:v2ray.com"]
|
||||
}],
|
||||
"hosts": {
|
||||
"v2ray.com": "127.0.0.1",
|
||||
"geosite:tld-cn": "10.0.0.1",
|
||||
"domain:example.com": "google.com"
|
||||
},
|
||||
"clientIp": "10.0.0.1"
|
||||
}`,
|
||||
Parser: parserCreator(),
|
||||
Output: &dns.Config{
|
||||
NameServer: []*dns.NameServer{
|
||||
{
|
||||
Address: &net.Endpoint{
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
},
|
||||
},
|
||||
Network: net.Network_UDP,
|
||||
Port: 5353,
|
||||
},
|
||||
PrioritizedDomain: []*dns.NameServer_PriorityDomain{
|
||||
{
|
||||
Type: dns.DomainMatchingType_Subdomain,
|
||||
Domain: "v2ray.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
StaticHosts: []*dns.Config_HostMapping{
|
||||
{
|
||||
Type: dns.DomainMatchingType_Subdomain,
|
||||
Domain: "example.com",
|
||||
ProxiedDomain: "google.com",
|
||||
},
|
||||
{
|
||||
Type: dns.DomainMatchingType_Subdomain,
|
||||
Domain: "cn",
|
||||
Ip: [][]byte{{10, 0, 0, 1}},
|
||||
},
|
||||
{
|
||||
Type: dns.DomainMatchingType_Subdomain,
|
||||
Domain: "xn--fiqs8s",
|
||||
Ip: [][]byte{{10, 0, 0, 1}},
|
||||
},
|
||||
{
|
||||
Type: dns.DomainMatchingType_Full,
|
||||
Domain: "v2ray.com",
|
||||
Ip: [][]byte{{127, 0, 0, 1}},
|
||||
},
|
||||
},
|
||||
ClientIp: []byte{10, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
28
infra/conf/dokodemo.go
Normal file
28
infra/conf/dokodemo.go
Normal file
@ -0,0 +1,28 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/proxy/dokodemo"
|
||||
)
|
||||
|
||||
type DokodemoConfig struct {
|
||||
Host *Address `json:"address"`
|
||||
PortValue uint16 `json:"port"`
|
||||
NetworkList *NetworkList `json:"network"`
|
||||
TimeoutValue uint32 `json:"timeout"`
|
||||
Redirect bool `json:"followRedirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
||||
config := new(dokodemo.Config)
|
||||
if v.Host != nil {
|
||||
config.Address = v.Host.Build()
|
||||
}
|
||||
config.Port = uint32(v.PortValue)
|
||||
config.Networks = v.NetworkList.Build()
|
||||
config.Timeout = v.TimeoutValue
|
||||
config.FollowRedirect = v.Redirect
|
||||
config.UserLevel = v.UserLevel
|
||||
return config, nil
|
||||
}
|
41
infra/conf/dokodemo_test.go
Normal file
41
infra/conf/dokodemo_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/dokodemo"
|
||||
)
|
||||
|
||||
func TestDokodemoConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(DokodemoConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"address": "8.8.8.8",
|
||||
"port": 53,
|
||||
"network": "tcp",
|
||||
"timeout": 10,
|
||||
"followRedirect": true,
|
||||
"userLevel": 1
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &dokodemo.Config{
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
},
|
||||
},
|
||||
Port: 53,
|
||||
Networks: []net.Network{net.Network_TCP},
|
||||
Timeout: 10,
|
||||
FollowRedirect: true,
|
||||
UserLevel: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
9
infra/conf/errors.generated.go
Normal file
9
infra/conf/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package conf
|
||||
|
||||
import "v2ray.com/core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
57
infra/conf/freedom.go
Normal file
57
infra/conf/freedom.go
Normal file
@ -0,0 +1,57 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
v2net "v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/proxy/freedom"
|
||||
)
|
||||
|
||||
type FreedomConfig struct {
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
Timeout *uint32 `json:"timeout"`
|
||||
Redirect string `json:"redirect"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *FreedomConfig) Build() (proto.Message, error) {
|
||||
config := new(freedom.Config)
|
||||
config.DomainStrategy = freedom.Config_AS_IS
|
||||
switch strings.ToLower(c.DomainStrategy) {
|
||||
case "useip", "use_ip":
|
||||
config.DomainStrategy = freedom.Config_USE_IP
|
||||
case "useip4", "useipv4", "use_ipv4", "use_ip_v4", "use_ip4":
|
||||
config.DomainStrategy = freedom.Config_USE_IP4
|
||||
case "useip6", "useipv6", "use_ipv6", "use_ip_v6", "use_ip6":
|
||||
config.DomainStrategy = freedom.Config_USE_IP6
|
||||
}
|
||||
config.Timeout = 600
|
||||
if c.Timeout != nil {
|
||||
config.Timeout = *c.Timeout
|
||||
}
|
||||
config.UserLevel = c.UserLevel
|
||||
if len(c.Redirect) > 0 {
|
||||
host, portStr, err := net.SplitHostPort(c.Redirect)
|
||||
if err != nil {
|
||||
return nil, newError("invalid redirect address: ", c.Redirect, ": ", err).Base(err)
|
||||
}
|
||||
port, err := v2net.PortFromString(portStr)
|
||||
if err != nil {
|
||||
return nil, newError("invalid redirect port: ", c.Redirect, ": ", err).Base(err)
|
||||
}
|
||||
config.DestinationOverride = &freedom.DestinationOverride{
|
||||
Server: &protocol.ServerEndpoint{
|
||||
Port: uint32(port),
|
||||
},
|
||||
}
|
||||
|
||||
if len(host) > 0 {
|
||||
config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host))
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
43
infra/conf/freedom_test.go
Normal file
43
infra/conf/freedom_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/freedom"
|
||||
)
|
||||
|
||||
func TestFreedomConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(FreedomConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"domainStrategy": "AsIs",
|
||||
"timeout": 10,
|
||||
"redirect": "127.0.0.1:3366",
|
||||
"userLevel": 1
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &freedom.Config{
|
||||
DomainStrategy: freedom.Config_AS_IS,
|
||||
Timeout: 10,
|
||||
DestinationOverride: &freedom.DestinationOverride{
|
||||
Server: &protocol.ServerEndpoint{
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 3366,
|
||||
},
|
||||
},
|
||||
UserLevel: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
36
infra/conf/general_test.go
Normal file
36
infra/conf/general_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/common"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func loadJSON(creator func() Buildable) func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
instance := creator()
|
||||
if err := json.Unmarshal([]byte(s), instance); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return instance.Build()
|
||||
}
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
Input string
|
||||
Parser func(string) (proto.Message, error)
|
||||
Output proto.Message
|
||||
}
|
||||
|
||||
func runMultiTestCase(t *testing.T, testCases []TestCase) {
|
||||
for _, testCase := range testCases {
|
||||
actual, err := testCase.Parser(testCase.Input)
|
||||
common.Must(err)
|
||||
if !proto.Equal(actual, testCase.Output) {
|
||||
t.Fatalf("Failed in test case:\n%s\nActual:\n%v\nExpected:\n%v", testCase.Input, actual, testCase.Output)
|
||||
}
|
||||
}
|
||||
}
|
35
infra/conf/http.go
Normal file
35
infra/conf/http.go
Normal file
@ -0,0 +1,35 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/proxy/http"
|
||||
)
|
||||
|
||||
type HttpAccount struct {
|
||||
Username string `json:"user"`
|
||||
Password string `json:"pass"`
|
||||
}
|
||||
|
||||
type HttpServerConfig struct {
|
||||
Timeout uint32 `json:"timeout"`
|
||||
Accounts []*HttpAccount `json:"accounts"`
|
||||
Transparent bool `json:"allowTransparent"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (c *HttpServerConfig) Build() (proto.Message, error) {
|
||||
config := &http.ServerConfig{
|
||||
Timeout: c.Timeout,
|
||||
AllowTransparent: c.Transparent,
|
||||
UserLevel: c.UserLevel,
|
||||
}
|
||||
|
||||
if len(c.Accounts) > 0 {
|
||||
config.Accounts = make(map[string]string)
|
||||
for _, account := range c.Accounts {
|
||||
config.Accounts[account.Username] = account.Password
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
39
infra/conf/http_test.go
Normal file
39
infra/conf/http_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/http"
|
||||
)
|
||||
|
||||
func TestHttpServerConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(HttpServerConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"timeout": 10,
|
||||
"accounts": [
|
||||
{
|
||||
"user": "my-username",
|
||||
"pass": "my-password"
|
||||
}
|
||||
],
|
||||
"allowTransparent": true,
|
||||
"userLevel": 1
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &http.ServerConfig{
|
||||
Accounts: map[string]string{
|
||||
"my-username": "my-password",
|
||||
},
|
||||
AllowTransparent: true,
|
||||
UserLevel: 1,
|
||||
Timeout: 10,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
133
infra/conf/json/reader.go
Normal file
133
infra/conf/json/reader.go
Normal file
@ -0,0 +1,133 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"v2ray.com/core/common/buf"
|
||||
)
|
||||
|
||||
// State is the internal state of parser.
|
||||
type State byte
|
||||
|
||||
const (
|
||||
StateContent State = iota
|
||||
StateEscape
|
||||
StateDoubleQuote
|
||||
StateDoubleQuoteEscape
|
||||
StateSingleQuote
|
||||
StateSingleQuoteEscape
|
||||
StateComment
|
||||
StateSlash
|
||||
StateMultilineComment
|
||||
StateMultilineCommentStar
|
||||
)
|
||||
|
||||
// Reader is a reader for filtering comments.
|
||||
// It supports Java style single and multi line comment syntax, and Python style single line comment syntax.
|
||||
type Reader struct {
|
||||
io.Reader
|
||||
|
||||
state State
|
||||
br *buf.BufferedReader
|
||||
}
|
||||
|
||||
// Read implements io.Reader.Read(). Buffer must be at least 3 bytes.
|
||||
func (v *Reader) Read(b []byte) (int, error) {
|
||||
if v.br == nil {
|
||||
v.br = &buf.BufferedReader{Reader: buf.NewReader(v.Reader)}
|
||||
}
|
||||
|
||||
p := b[:0]
|
||||
for len(p) < len(b)-2 {
|
||||
x, err := v.br.ReadByte()
|
||||
if err != nil {
|
||||
if len(p) == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
switch v.state {
|
||||
case StateContent:
|
||||
switch x {
|
||||
case '"':
|
||||
v.state = StateDoubleQuote
|
||||
p = append(p, x)
|
||||
case '\'':
|
||||
v.state = StateSingleQuote
|
||||
p = append(p, x)
|
||||
case '\\':
|
||||
v.state = StateEscape
|
||||
case '#':
|
||||
v.state = StateComment
|
||||
case '/':
|
||||
v.state = StateSlash
|
||||
default:
|
||||
p = append(p, x)
|
||||
}
|
||||
case StateEscape:
|
||||
p = append(p, '\\', x)
|
||||
v.state = StateContent
|
||||
case StateDoubleQuote:
|
||||
switch x {
|
||||
case '"':
|
||||
v.state = StateContent
|
||||
p = append(p, x)
|
||||
case '\\':
|
||||
v.state = StateDoubleQuoteEscape
|
||||
default:
|
||||
p = append(p, x)
|
||||
}
|
||||
case StateDoubleQuoteEscape:
|
||||
p = append(p, '\\', x)
|
||||
v.state = StateDoubleQuote
|
||||
case StateSingleQuote:
|
||||
switch x {
|
||||
case '\'':
|
||||
v.state = StateContent
|
||||
p = append(p, x)
|
||||
case '\\':
|
||||
v.state = StateSingleQuoteEscape
|
||||
default:
|
||||
p = append(p, x)
|
||||
}
|
||||
case StateSingleQuoteEscape:
|
||||
p = append(p, '\\', x)
|
||||
v.state = StateSingleQuote
|
||||
case StateComment:
|
||||
if x == '\n' {
|
||||
v.state = StateContent
|
||||
p = append(p, '\n')
|
||||
}
|
||||
case StateSlash:
|
||||
switch x {
|
||||
case '/':
|
||||
v.state = StateComment
|
||||
case '*':
|
||||
v.state = StateMultilineComment
|
||||
default:
|
||||
p = append(p, '/', x)
|
||||
}
|
||||
case StateMultilineComment:
|
||||
switch x {
|
||||
case '*':
|
||||
v.state = StateMultilineCommentStar
|
||||
case '\n':
|
||||
p = append(p, '\n')
|
||||
}
|
||||
case StateMultilineCommentStar:
|
||||
switch x {
|
||||
case '/':
|
||||
v.state = StateContent
|
||||
case '*':
|
||||
// Stay
|
||||
case '\n':
|
||||
p = append(p, '\n')
|
||||
default:
|
||||
v.state = StateMultilineComment
|
||||
}
|
||||
default:
|
||||
panic("Unknown state.")
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
97
infra/conf/json/reader_test.go
Normal file
97
infra/conf/json/reader_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
. "v2ray.com/core/infra/conf/json"
|
||||
)
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
data := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{
|
||||
`
|
||||
content #comment 1
|
||||
#comment 2
|
||||
content 2`,
|
||||
`
|
||||
content
|
||||
|
||||
content 2`},
|
||||
{`content`, `content`},
|
||||
{" ", " "},
|
||||
{`con/*abcd*/tent`, "content"},
|
||||
{`
|
||||
text // adlkhdf /*
|
||||
//comment adfkj
|
||||
text 2*/`, `
|
||||
text
|
||||
|
||||
text 2*`},
|
||||
{`"//"content`, `"//"content`},
|
||||
{`abcd'//'abcd`, `abcd'//'abcd`},
|
||||
{`"\""`, `"\""`},
|
||||
{`\"/*abcd*/\"`, `\"\"`},
|
||||
}
|
||||
|
||||
for _, testCase := range data {
|
||||
reader := &Reader{
|
||||
Reader: bytes.NewReader([]byte(testCase.input)),
|
||||
}
|
||||
|
||||
actual := make([]byte, 1024)
|
||||
n, err := reader.Read(actual)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(string(actual[:n]), testCase.output); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader1(t *testing.T) {
|
||||
type dataStruct struct {
|
||||
input string
|
||||
output string
|
||||
}
|
||||
|
||||
bufLen := 8
|
||||
|
||||
data := []dataStruct{
|
||||
{"loooooooooooooooooooooooooooooooooooooooog", "loooooooooooooooooooooooooooooooooooooooog"},
|
||||
{`{"t": "\/testlooooooooooooooooooooooooooooong"}`, `{"t": "\/testlooooooooooooooooooooooooooooong"}`},
|
||||
{`{"t": "\/test"}`, `{"t": "\/test"}`},
|
||||
{`"\// fake comment"`, `"\// fake comment"`},
|
||||
{`"\/\/\/\/\/"`, `"\/\/\/\/\/"`},
|
||||
}
|
||||
|
||||
for _, testCase := range data {
|
||||
reader := &Reader{
|
||||
Reader: bytes.NewReader([]byte(testCase.input)),
|
||||
}
|
||||
target := make([]byte, 0)
|
||||
buf := make([]byte, bufLen)
|
||||
var n int
|
||||
var err error
|
||||
for n, err = reader.Read(buf); err == nil; n, err = reader.Read(buf) {
|
||||
if n > len(buf) {
|
||||
t.Error("n: ", n)
|
||||
}
|
||||
target = append(target, buf[:n]...)
|
||||
buf = make([]byte, bufLen)
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
t.Error("error: ", err)
|
||||
}
|
||||
if string(target) != testCase.output {
|
||||
t.Error("got ", string(target), " want ", testCase.output)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
83
infra/conf/loader.go
Normal file
83
infra/conf/loader.go
Normal file
@ -0,0 +1,83 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ConfigCreator func() interface{}
|
||||
|
||||
type ConfigCreatorCache map[string]ConfigCreator
|
||||
|
||||
func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error {
|
||||
if _, found := v[id]; found {
|
||||
return newError(id, " already registered.").AtError()
|
||||
}
|
||||
|
||||
v[id] = creator
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v ConfigCreatorCache) CreateConfig(id string) (interface{}, error) {
|
||||
creator, found := v[id]
|
||||
if !found {
|
||||
return nil, newError("unknown config id: ", id)
|
||||
}
|
||||
return creator(), nil
|
||||
}
|
||||
|
||||
type JSONConfigLoader struct {
|
||||
cache ConfigCreatorCache
|
||||
idKey string
|
||||
configKey string
|
||||
}
|
||||
|
||||
func NewJSONConfigLoader(cache ConfigCreatorCache, idKey string, configKey string) *JSONConfigLoader {
|
||||
return &JSONConfigLoader{
|
||||
idKey: idKey,
|
||||
configKey: configKey,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *JSONConfigLoader) LoadWithID(raw []byte, id string) (interface{}, error) {
|
||||
id = strings.ToLower(id)
|
||||
config, err := v.cache.CreateConfig(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(raw, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) {
|
||||
var obj map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
rawID, found := obj[v.idKey]
|
||||
if !found {
|
||||
return nil, "", newError(v.idKey, " not found in JSON context").AtError()
|
||||
}
|
||||
var id string
|
||||
if err := json.Unmarshal(rawID, &id); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
rawConfig := json.RawMessage(raw)
|
||||
if len(v.configKey) > 0 {
|
||||
configValue, found := obj[v.configKey]
|
||||
if found {
|
||||
rawConfig = configValue
|
||||
} else {
|
||||
// Default to empty json object.
|
||||
rawConfig = json.RawMessage([]byte("{}"))
|
||||
}
|
||||
}
|
||||
config, err := v.LoadWithID([]byte(rawConfig), id)
|
||||
if err != nil {
|
||||
return nil, id, err
|
||||
}
|
||||
return config, id, nil
|
||||
}
|
57
infra/conf/log.go
Normal file
57
infra/conf/log.go
Normal file
@ -0,0 +1,57 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/app/log"
|
||||
clog "v2ray.com/core/common/log"
|
||||
)
|
||||
|
||||
func DefaultLogConfig() *log.Config {
|
||||
return &log.Config{
|
||||
AccessLogType: log.LogType_None,
|
||||
ErrorLogType: log.LogType_Console,
|
||||
ErrorLogLevel: clog.Severity_Warning,
|
||||
}
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
AccessLog string `json:"access"`
|
||||
ErrorLog string `json:"error"`
|
||||
LogLevel string `json:"loglevel"`
|
||||
}
|
||||
|
||||
func (v *LogConfig) Build() *log.Config {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
config := &log.Config{
|
||||
ErrorLogType: log.LogType_Console,
|
||||
AccessLogType: log.LogType_Console,
|
||||
}
|
||||
|
||||
if len(v.AccessLog) > 0 {
|
||||
config.AccessLogPath = v.AccessLog
|
||||
config.AccessLogType = log.LogType_File
|
||||
}
|
||||
if len(v.ErrorLog) > 0 {
|
||||
config.ErrorLogPath = v.ErrorLog
|
||||
config.ErrorLogType = log.LogType_File
|
||||
}
|
||||
|
||||
level := strings.ToLower(v.LogLevel)
|
||||
switch level {
|
||||
case "debug":
|
||||
config.ErrorLogLevel = clog.Severity_Debug
|
||||
case "info":
|
||||
config.ErrorLogLevel = clog.Severity_Info
|
||||
case "error":
|
||||
config.ErrorLogLevel = clog.Severity_Error
|
||||
case "none":
|
||||
config.ErrorLogType = log.LogType_None
|
||||
config.AccessLogType = log.LogType_None
|
||||
default:
|
||||
config.ErrorLogLevel = clog.Severity_Warning
|
||||
}
|
||||
return config
|
||||
}
|
69
infra/conf/mtproto.go
Normal file
69
infra/conf/mtproto.go
Normal file
@ -0,0 +1,69 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/proxy/mtproto"
|
||||
)
|
||||
|
||||
type MTProtoAccount struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (a *MTProtoAccount) Build() (*mtproto.Account, error) {
|
||||
if len(a.Secret) != 32 {
|
||||
return nil, newError("MTProto secret must have 32 chars")
|
||||
}
|
||||
secret, err := hex.DecodeString(a.Secret)
|
||||
if err != nil {
|
||||
return nil, newError("failed to decode secret: ", a.Secret).Base(err)
|
||||
}
|
||||
return &mtproto.Account{
|
||||
Secret: secret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MTProtoServerConfig struct {
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
|
||||
func (c *MTProtoServerConfig) Build() (proto.Message, error) {
|
||||
config := &mtproto.ServerConfig{}
|
||||
|
||||
if len(c.Users) == 0 {
|
||||
return nil, newError("zero MTProto users configured.")
|
||||
}
|
||||
config.User = make([]*protocol.User, len(c.Users))
|
||||
for idx, rawData := range c.Users {
|
||||
user := new(protocol.User)
|
||||
if err := json.Unmarshal(rawData, user); err != nil {
|
||||
return nil, newError("invalid MTProto user").Base(err)
|
||||
}
|
||||
account := new(MTProtoAccount)
|
||||
if err := json.Unmarshal(rawData, account); err != nil {
|
||||
return nil, newError("invalid MTProto user").Base(err)
|
||||
}
|
||||
accountProto, err := account.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse MTProto user").Base(err)
|
||||
}
|
||||
user.Account = serial.ToTypedMessage(accountProto)
|
||||
config.User[idx] = user
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type MTProtoClientConfig struct {
|
||||
}
|
||||
|
||||
func (c *MTProtoClientConfig) Build() (proto.Message, error) {
|
||||
config := new(mtproto.ClientConfig)
|
||||
return config, nil
|
||||
}
|
40
infra/conf/mtproto_test.go
Normal file
40
infra/conf/mtproto_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/mtproto"
|
||||
)
|
||||
|
||||
func TestMTProtoServerConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(MTProtoServerConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"users": [{
|
||||
"email": "love@v2ray.com",
|
||||
"level": 1,
|
||||
"secret": "b0cbcef5a486d9636472ac27f8e11a9d"
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &mtproto.ServerConfig{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Email: "love@v2ray.com",
|
||||
Level: 1,
|
||||
Account: serial.ToTypedMessage(&mtproto.Account{
|
||||
Secret: []byte{176, 203, 206, 245, 164, 134, 217, 99, 100, 114, 172, 39, 248, 225, 26, 157},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
96
infra/conf/policy.go
Normal file
96
infra/conf/policy.go
Normal file
@ -0,0 +1,96 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"v2ray.com/core/app/policy"
|
||||
)
|
||||
|
||||
type Policy struct {
|
||||
Handshake *uint32 `json:"handshake"`
|
||||
ConnectionIdle *uint32 `json:"connIdle"`
|
||||
UplinkOnly *uint32 `json:"uplinkOnly"`
|
||||
DownlinkOnly *uint32 `json:"downlinkOnly"`
|
||||
StatsUserUplink bool `json:"statsUserUplink"`
|
||||
StatsUserDownlink bool `json:"statsUserDownlink"`
|
||||
BufferSize *int32 `json:"bufferSize"`
|
||||
}
|
||||
|
||||
func (t *Policy) Build() (*policy.Policy, error) {
|
||||
config := new(policy.Policy_Timeout)
|
||||
if t.Handshake != nil {
|
||||
config.Handshake = &policy.Second{Value: *t.Handshake}
|
||||
}
|
||||
if t.ConnectionIdle != nil {
|
||||
config.ConnectionIdle = &policy.Second{Value: *t.ConnectionIdle}
|
||||
}
|
||||
if t.UplinkOnly != nil {
|
||||
config.UplinkOnly = &policy.Second{Value: *t.UplinkOnly}
|
||||
}
|
||||
if t.DownlinkOnly != nil {
|
||||
config.DownlinkOnly = &policy.Second{Value: *t.DownlinkOnly}
|
||||
}
|
||||
|
||||
p := &policy.Policy{
|
||||
Timeout: config,
|
||||
Stats: &policy.Policy_Stats{
|
||||
UserUplink: t.StatsUserUplink,
|
||||
UserDownlink: t.StatsUserDownlink,
|
||||
},
|
||||
}
|
||||
|
||||
if t.BufferSize != nil {
|
||||
bs := int32(-1)
|
||||
if *t.BufferSize >= 0 {
|
||||
bs = (*t.BufferSize) * 1024
|
||||
}
|
||||
p.Buffer = &policy.Policy_Buffer{
|
||||
Connection: bs,
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type SystemPolicy struct {
|
||||
StatsInboundUplink bool `json:"statsInboundUplink"`
|
||||
StatsInboundDownlink bool `json:"statsInboundDownlink"`
|
||||
}
|
||||
|
||||
func (p *SystemPolicy) Build() (*policy.SystemPolicy, error) {
|
||||
return &policy.SystemPolicy{
|
||||
Stats: &policy.SystemPolicy_Stats{
|
||||
InboundUplink: p.StatsInboundUplink,
|
||||
InboundDownlink: p.StatsInboundDownlink,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PolicyConfig struct {
|
||||
Levels map[uint32]*Policy `json:"levels"`
|
||||
System *SystemPolicy `json:"system"`
|
||||
}
|
||||
|
||||
func (c *PolicyConfig) Build() (*policy.Config, error) {
|
||||
levels := make(map[uint32]*policy.Policy)
|
||||
for l, p := range c.Levels {
|
||||
if p != nil {
|
||||
pp, err := p.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
levels[l] = pp
|
||||
}
|
||||
}
|
||||
config := &policy.Config{
|
||||
Level: levels,
|
||||
}
|
||||
|
||||
if c.System != nil {
|
||||
sc, err := c.System.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.System = sc
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
40
infra/conf/policy_test.go
Normal file
40
infra/conf/policy_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func TestBufferSize(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input int32
|
||||
Output int32
|
||||
}{
|
||||
{
|
||||
Input: 0,
|
||||
Output: 0,
|
||||
},
|
||||
{
|
||||
Input: -1,
|
||||
Output: -1,
|
||||
},
|
||||
{
|
||||
Input: 1,
|
||||
Output: 1024,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
bs := int32(c.Input)
|
||||
pConf := Policy{
|
||||
BufferSize: &bs,
|
||||
}
|
||||
p, err := pConf.Build()
|
||||
common.Must(err)
|
||||
if p.Buffer.Connection != c.Output {
|
||||
t.Error("expected buffer size ", c.Output, " but got ", p.Buffer.Connection)
|
||||
}
|
||||
}
|
||||
}
|
56
infra/conf/reverse.go
Normal file
56
infra/conf/reverse.go
Normal file
@ -0,0 +1,56 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/app/reverse"
|
||||
)
|
||||
|
||||
type BridgeConfig struct {
|
||||
Tag string `json:"tag"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func (c *BridgeConfig) Build() (*reverse.BridgeConfig, error) {
|
||||
return &reverse.BridgeConfig{
|
||||
Tag: c.Tag,
|
||||
Domain: c.Domain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PortalConfig struct {
|
||||
Tag string `json:"tag"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func (c *PortalConfig) Build() (*reverse.PortalConfig, error) {
|
||||
return &reverse.PortalConfig{
|
||||
Tag: c.Tag,
|
||||
Domain: c.Domain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ReverseConfig struct {
|
||||
Bridges []BridgeConfig `json:"bridges"`
|
||||
Portals []PortalConfig `json:"portals"`
|
||||
}
|
||||
|
||||
func (c *ReverseConfig) Build() (proto.Message, error) {
|
||||
config := &reverse.Config{}
|
||||
for _, bconfig := range c.Bridges {
|
||||
b, err := bconfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.BridgeConfig = append(config.BridgeConfig, b)
|
||||
}
|
||||
|
||||
for _, pconfig := range c.Portals {
|
||||
p, err := pconfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.PortalConfig = append(config.PortalConfig, p)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
45
infra/conf/reverse_test.go
Normal file
45
infra/conf/reverse_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/app/reverse"
|
||||
"v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func TestReverseConfig(t *testing.T) {
|
||||
creator := func() conf.Buildable {
|
||||
return new(conf.ReverseConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"bridges": [{
|
||||
"tag": "test",
|
||||
"domain": "test.v2ray.com"
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &reverse.Config{
|
||||
BridgeConfig: []*reverse.BridgeConfig{
|
||||
{Tag: "test", Domain: "test.v2ray.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"portals": [{
|
||||
"tag": "test",
|
||||
"domain": "test.v2ray.com"
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &reverse.Config{
|
||||
PortalConfig: []*reverse.PortalConfig{
|
||||
{Tag: "test", Domain: "test.v2ray.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
502
infra/conf/router.go
Normal file
502
infra/conf/router.go
Normal file
@ -0,0 +1,502 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/app/router"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/platform/filesystem"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
type RouterRulesConfig struct {
|
||||
RuleList []json.RawMessage `json:"rules"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
}
|
||||
|
||||
type BalancingRule struct {
|
||||
Tag string `json:"tag"`
|
||||
Selectors StringList `json:"selector"`
|
||||
}
|
||||
|
||||
func (r *BalancingRule) Build() (*router.BalancingRule, error) {
|
||||
if len(r.Tag) == 0 {
|
||||
return nil, newError("empty balancer tag")
|
||||
}
|
||||
if len(r.Selectors) == 0 {
|
||||
return nil, newError("empty selector list")
|
||||
}
|
||||
|
||||
return &router.BalancingRule{
|
||||
Tag: r.Tag,
|
||||
OutboundSelector: []string(r.Selectors),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type RouterConfig struct {
|
||||
Settings *RouterRulesConfig `json:"settings"` // Deprecated
|
||||
RuleList []json.RawMessage `json:"rules"`
|
||||
DomainStrategy *string `json:"domainStrategy"`
|
||||
Balancers []*BalancingRule `json:"balancers"`
|
||||
}
|
||||
|
||||
func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
||||
ds := ""
|
||||
if c.DomainStrategy != nil {
|
||||
ds = *c.DomainStrategy
|
||||
} else if c.Settings != nil {
|
||||
ds = c.Settings.DomainStrategy
|
||||
}
|
||||
|
||||
switch strings.ToLower(ds) {
|
||||
case "alwaysip":
|
||||
return router.Config_UseIp
|
||||
case "ipifnonmatch":
|
||||
return router.Config_IpIfNonMatch
|
||||
case "ipondemand":
|
||||
return router.Config_IpOnDemand
|
||||
default:
|
||||
return router.Config_AsIs
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RouterConfig) Build() (*router.Config, error) {
|
||||
config := new(router.Config)
|
||||
config.DomainStrategy = c.getDomainStrategy()
|
||||
|
||||
rawRuleList := c.RuleList
|
||||
if c.Settings != nil {
|
||||
rawRuleList = append(c.RuleList, c.Settings.RuleList...)
|
||||
}
|
||||
for _, rawRule := range rawRuleList {
|
||||
rule, err := ParseRule(rawRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Rule = append(config.Rule, rule)
|
||||
}
|
||||
for _, rawBalancer := range c.Balancers {
|
||||
balancer, err := rawBalancer.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.BalancingRule = append(config.BalancingRule, balancer)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type RouterRule struct {
|
||||
Type string `json:"type"`
|
||||
OutboundTag string `json:"outboundTag"`
|
||||
BalancerTag string `json:"balancerTag"`
|
||||
}
|
||||
|
||||
func ParseIP(s string) (*router.CIDR, error) {
|
||||
var addr, mask string
|
||||
i := strings.Index(s, "/")
|
||||
if i < 0 {
|
||||
addr = s
|
||||
} else {
|
||||
addr = s[:i]
|
||||
mask = s[i+1:]
|
||||
}
|
||||
ip := net.ParseAddress(addr)
|
||||
switch ip.Family() {
|
||||
case net.AddressFamilyIPv4:
|
||||
bits := uint32(32)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 32 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
case net.AddressFamilyIPv6:
|
||||
bits := uint32(128)
|
||||
if len(mask) > 0 {
|
||||
bits64, err := strconv.ParseUint(mask, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newError("invalid network mask for router: ", mask).Base(err)
|
||||
}
|
||||
bits = uint32(bits64)
|
||||
}
|
||||
if bits > 128 {
|
||||
return nil, newError("invalid network mask for router: ", bits)
|
||||
}
|
||||
return &router.CIDR{
|
||||
Ip: []byte(ip.IP()),
|
||||
Prefix: bits,
|
||||
}, nil
|
||||
default:
|
||||
return nil, newError("unsupported address for router: ", s)
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
return loadIP("geoip.dat", country)
|
||||
}
|
||||
|
||||
func loadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if geoip.CountryCode == country {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("country not found: " + country)
|
||||
}
|
||||
|
||||
func loadSite(filename, country string) ([]*router.Domain, error) {
|
||||
geositeBytes, err := filesystem.ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, newError("failed to open file: ", filename).Base(err)
|
||||
}
|
||||
var geositeList router.GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, site := range geositeList.Entry {
|
||||
if site.CountryCode == country {
|
||||
return site.Domain, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, newError("country not found: " + country)
|
||||
}
|
||||
|
||||
type AttributeMatcher interface {
|
||||
Match(*router.Domain) bool
|
||||
}
|
||||
|
||||
type BooleanMatcher string
|
||||
|
||||
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if attr.Key == string(m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
for _, matcher := range al.matcher {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
lc := strings.ToLower(attr)
|
||||
al.matcher = append(al.matcher, BooleanMatcher(lc))
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
||||
parts := strings.Split(siteWithAttr, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, newError("empty site")
|
||||
}
|
||||
country := strings.ToUpper(parts[0])
|
||||
attrs := parseAttrs(parts[1:])
|
||||
domains, err := loadSite(file, country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if attrs.IsEmpty() {
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredDomains, nil
|
||||
}
|
||||
|
||||
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
||||
if strings.HasPrefix(domain, "geosite:") {
|
||||
country := strings.ToUpper(domain[8:])
|
||||
domains, err := loadGeositeWithAttr("geosite.dat", country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geosite: ", country).Base(err)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, "ext:") {
|
||||
kv := strings.Split(domain[4:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", domain)
|
||||
}
|
||||
filename := kv[0]
|
||||
country := kv[1]
|
||||
domains, err := loadGeositeWithAttr(filename, country)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
domainRule := new(router.Domain)
|
||||
switch {
|
||||
case strings.HasPrefix(domain, "regexp:"):
|
||||
domainRule.Type = router.Domain_Regex
|
||||
domainRule.Value = domain[7:]
|
||||
case strings.HasPrefix(domain, "domain:"):
|
||||
domainRule.Type = router.Domain_Domain
|
||||
domainRule.Value = domain[7:]
|
||||
case strings.HasPrefix(domain, "full:"):
|
||||
domainRule.Type = router.Domain_Full
|
||||
domainRule.Value = domain[5:]
|
||||
default:
|
||||
domainRule.Type = router.Domain_Plain
|
||||
domainRule.Value = domain
|
||||
}
|
||||
return []*router.Domain{domainRule}, nil
|
||||
}
|
||||
|
||||
func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
||||
var geoipList []*router.GeoIP
|
||||
var customCidrs []*router.CIDR
|
||||
|
||||
for _, ip := range ips {
|
||||
if strings.HasPrefix(ip, "geoip:") {
|
||||
country := ip[6:]
|
||||
geoip, err := loadGeoIP(strings.ToUpper(country))
|
||||
if err != nil {
|
||||
return nil, newError("failed to load GeoIP: ", country).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(country),
|
||||
Cidr: geoip,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ip, "ext:") {
|
||||
kv := strings.Split(ip[4:], ":")
|
||||
if len(kv) != 2 {
|
||||
return nil, newError("invalid external resource: ", ip)
|
||||
}
|
||||
|
||||
filename := kv[0]
|
||||
country := kv[1]
|
||||
geoip, err := loadGeoIP(strings.ToUpper(country))
|
||||
if err != nil {
|
||||
return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
|
||||
}
|
||||
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
CountryCode: strings.ToUpper(filename + "_" + country),
|
||||
Cidr: geoip,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ipRule, err := ParseIP(ip)
|
||||
if err != nil {
|
||||
return nil, newError("invalid IP: ", ip).Base(err)
|
||||
}
|
||||
customCidrs = append(customCidrs, ipRule)
|
||||
}
|
||||
|
||||
if len(customCidrs) > 0 {
|
||||
geoipList = append(geoipList, &router.GeoIP{
|
||||
Cidr: customCidrs,
|
||||
})
|
||||
}
|
||||
|
||||
return geoipList, nil
|
||||
}
|
||||
|
||||
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
type RawFieldRule struct {
|
||||
RouterRule
|
||||
Domain *StringList `json:"domain"`
|
||||
IP *StringList `json:"ip"`
|
||||
Port *PortRange `json:"port"`
|
||||
Network *NetworkList `json:"network"`
|
||||
SourceIP *StringList `json:"source"`
|
||||
User *StringList `json:"user"`
|
||||
InboundTag *StringList `json:"inboundTag"`
|
||||
Protocols *StringList `json:"protocol"`
|
||||
}
|
||||
rawFieldRule := new(RawFieldRule)
|
||||
err := json.Unmarshal(msg, rawFieldRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rule := new(router.RoutingRule)
|
||||
if len(rawFieldRule.OutboundTag) > 0 {
|
||||
rule.TargetTag = &router.RoutingRule_Tag{
|
||||
Tag: rawFieldRule.OutboundTag,
|
||||
}
|
||||
} else if len(rawFieldRule.BalancerTag) > 0 {
|
||||
rule.TargetTag = &router.RoutingRule_BalancingTag{
|
||||
BalancingTag: rawFieldRule.BalancerTag,
|
||||
}
|
||||
} else {
|
||||
return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
|
||||
}
|
||||
|
||||
if rawFieldRule.Domain != nil {
|
||||
for _, domain := range *rawFieldRule.Domain {
|
||||
rules, err := parseDomainRule(domain)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse domain rule: ", domain).Base(err)
|
||||
}
|
||||
rule.Domain = append(rule.Domain, rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.IP != nil {
|
||||
geoipList, err := toCidrList(*rawFieldRule.IP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.Geoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.Port != nil {
|
||||
rule.PortRange = rawFieldRule.Port.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.Network != nil {
|
||||
rule.Networks = rawFieldRule.Network.Build()
|
||||
}
|
||||
|
||||
if rawFieldRule.SourceIP != nil {
|
||||
geoipList, err := toCidrList(*rawFieldRule.SourceIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.SourceGeoip = geoipList
|
||||
}
|
||||
|
||||
if rawFieldRule.User != nil {
|
||||
for _, s := range *rawFieldRule.User {
|
||||
rule.UserEmail = append(rule.UserEmail, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.InboundTag != nil {
|
||||
for _, s := range *rawFieldRule.InboundTag {
|
||||
rule.InboundTag = append(rule.InboundTag, s)
|
||||
}
|
||||
}
|
||||
|
||||
if rawFieldRule.Protocols != nil {
|
||||
for _, s := range *rawFieldRule.Protocols {
|
||||
rule.Protocol = append(rule.Protocol, s)
|
||||
}
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(msg, rawRule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid router rule").Base(err)
|
||||
}
|
||||
if rawRule.Type == "field" {
|
||||
fieldrule, err := parseFieldRule(msg)
|
||||
if err != nil {
|
||||
return nil, newError("invalid field rule").Base(err)
|
||||
}
|
||||
return fieldrule, nil
|
||||
}
|
||||
if rawRule.Type == "chinaip" {
|
||||
chinaiprule, err := parseChinaIPRule(msg)
|
||||
if err != nil {
|
||||
return nil, newError("invalid chinaip rule").Base(err)
|
||||
}
|
||||
return chinaiprule, nil
|
||||
}
|
||||
if rawRule.Type == "chinasites" {
|
||||
chinasitesrule, err := parseChinaSitesRule(msg)
|
||||
if err != nil {
|
||||
return nil, newError("invalid chinasites rule").Base(err)
|
||||
}
|
||||
return chinasitesrule, nil
|
||||
}
|
||||
return nil, newError("unknown router rule type: ", rawRule.Type)
|
||||
}
|
||||
|
||||
func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(data, rawRule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid router rule").Base(err)
|
||||
}
|
||||
chinaIPs, err := loadGeoIP("CN")
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geoip:cn").Base(err)
|
||||
}
|
||||
return &router.RoutingRule{
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: rawRule.OutboundTag,
|
||||
},
|
||||
Cidr: chinaIPs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
|
||||
rawRule := new(RouterRule)
|
||||
err := json.Unmarshal(data, rawRule)
|
||||
if err != nil {
|
||||
return nil, newError("invalid router rule").Base(err).AtError()
|
||||
}
|
||||
domains, err := loadGeositeWithAttr("geosite.dat", "CN")
|
||||
if err != nil {
|
||||
return nil, newError("failed to load geosite:cn.").Base(err)
|
||||
}
|
||||
return &router.RoutingRule{
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: rawRule.OutboundTag,
|
||||
},
|
||||
Domain: domains,
|
||||
}, nil
|
||||
}
|
233
infra/conf/router_test.go
Normal file
233
infra/conf/router_test.go
Normal file
@ -0,0 +1,233 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/app/router"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
)
|
||||
|
||||
func TestRouterConfig(t *testing.T) {
|
||||
createParser := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
config := new(RouterConfig)
|
||||
if err := json.Unmarshal([]byte(s), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Build()
|
||||
}
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"strategy": "rules",
|
||||
"settings": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"baidu.com",
|
||||
"qq.com"
|
||||
],
|
||||
"outboundTag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"10.0.0.0/8",
|
||||
"::1/128"
|
||||
],
|
||||
"outboundTag": "test"
|
||||
}
|
||||
]
|
||||
},
|
||||
"balancers": [
|
||||
{
|
||||
"tag": "b1",
|
||||
"selector": ["test"]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &router.Config{
|
||||
DomainStrategy: router.Config_AsIs,
|
||||
BalancingRule: []*router.BalancingRule{
|
||||
{
|
||||
Tag: "b1",
|
||||
OutboundSelector: []string{"test"},
|
||||
},
|
||||
},
|
||||
Rule: []*router.RoutingRule{
|
||||
{
|
||||
Domain: []*router.Domain{
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "baidu.com",
|
||||
},
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "qq.com",
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
{
|
||||
Geoip: []*router.GeoIP{
|
||||
{
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
{
|
||||
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
Prefix: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"strategy": "rules",
|
||||
"settings": {
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"baidu.com",
|
||||
"qq.com"
|
||||
],
|
||||
"outboundTag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"10.0.0.0/8",
|
||||
"::1/128"
|
||||
],
|
||||
"outboundTag": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &router.Config{
|
||||
DomainStrategy: router.Config_IpIfNonMatch,
|
||||
Rule: []*router.RoutingRule{
|
||||
{
|
||||
Domain: []*router.Domain{
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "baidu.com",
|
||||
},
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "qq.com",
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
{
|
||||
Geoip: []*router.GeoIP{
|
||||
{
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
{
|
||||
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
Prefix: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"baidu.com",
|
||||
"qq.com"
|
||||
],
|
||||
"outboundTag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"10.0.0.0/8",
|
||||
"::1/128"
|
||||
],
|
||||
"outboundTag": "test"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &router.Config{
|
||||
DomainStrategy: router.Config_AsIs,
|
||||
Rule: []*router.RoutingRule{
|
||||
{
|
||||
Domain: []*router.Domain{
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "baidu.com",
|
||||
},
|
||||
{
|
||||
Type: router.Domain_Plain,
|
||||
Value: "qq.com",
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
{
|
||||
Geoip: []*router.GeoIP{
|
||||
{
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
{
|
||||
Ip: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
Prefix: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
9
infra/conf/serial/errors.generated.go
Normal file
9
infra/conf/serial/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package serial
|
||||
|
||||
import "v2ray.com/core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
71
infra/conf/serial/loader.go
Normal file
71
infra/conf/serial/loader.go
Normal file
@ -0,0 +1,71 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"v2ray.com/core"
|
||||
"v2ray.com/core/common/errors"
|
||||
"v2ray.com/core/infra/conf"
|
||||
json_reader "v2ray.com/core/infra/conf/json"
|
||||
)
|
||||
|
||||
type offset struct {
|
||||
line int
|
||||
char int
|
||||
}
|
||||
|
||||
func findOffset(b []byte, o int) *offset {
|
||||
if o >= len(b) || o < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := 1
|
||||
char := 0
|
||||
for i, x := range b {
|
||||
if i == o {
|
||||
break
|
||||
}
|
||||
if x == '\n' {
|
||||
line++
|
||||
char = 0
|
||||
} else {
|
||||
char++
|
||||
}
|
||||
}
|
||||
|
||||
return &offset{line: line, char: char}
|
||||
}
|
||||
|
||||
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
|
||||
jsonConfig := &conf.Config{}
|
||||
|
||||
jsonContent := bytes.NewBuffer(make([]byte, 0, 10240))
|
||||
jsonReader := io.TeeReader(&json_reader.Reader{
|
||||
Reader: reader,
|
||||
}, jsonContent)
|
||||
decoder := json.NewDecoder(jsonReader)
|
||||
|
||||
if err := decoder.Decode(jsonConfig); err != nil {
|
||||
var pos *offset
|
||||
cause := errors.Cause(err)
|
||||
switch tErr := cause.(type) {
|
||||
case *json.SyntaxError:
|
||||
pos = findOffset(jsonContent.Bytes(), int(tErr.Offset))
|
||||
case *json.UnmarshalTypeError:
|
||||
pos = findOffset(jsonContent.Bytes(), int(tErr.Offset))
|
||||
}
|
||||
if pos != nil {
|
||||
return nil, newError("failed to read config file at line ", pos.line, " char ", pos.char).Base(err)
|
||||
}
|
||||
return nil, newError("failed to read config file").Base(err)
|
||||
}
|
||||
|
||||
pbConfig, err := jsonConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse json config").Base(err)
|
||||
}
|
||||
|
||||
return pbConfig, nil
|
||||
}
|
63
infra/conf/serial/loader_test.go
Normal file
63
infra/conf/serial/loader_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/infra/conf/serial"
|
||||
)
|
||||
|
||||
func TestLoaderError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
Input: `{
|
||||
"log": {
|
||||
// abcd
|
||||
0,
|
||||
"loglevel": "info"
|
||||
}
|
||||
}`,
|
||||
Output: "line 4 char 6",
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"log": {
|
||||
// abcd
|
||||
"loglevel": "info",
|
||||
}
|
||||
}`,
|
||||
Output: "line 5 char 5",
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"port": 1,
|
||||
"inbounds": [{
|
||||
"protocol": "test"
|
||||
}]
|
||||
}`,
|
||||
Output: "parse json config",
|
||||
},
|
||||
{
|
||||
Input: `{
|
||||
"inbounds": [{
|
||||
"port": 1,
|
||||
"listen": 0,
|
||||
"protocol": "test"
|
||||
}]
|
||||
}`,
|
||||
Output: "line 1 char 1",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
reader := bytes.NewReader([]byte(testCase.Input))
|
||||
_, err := serial.LoadJSONConfig(reader)
|
||||
errString := err.Error()
|
||||
if !strings.Contains(errString, testCase.Output) {
|
||||
t.Error("unexpected output from json: ", testCase.Input, ". expected ", testCase.Output, ", but actually ", errString)
|
||||
}
|
||||
}
|
||||
}
|
3
infra/conf/serial/serial.go
Normal file
3
infra/conf/serial/serial.go
Normal file
@ -0,0 +1,3 @@
|
||||
package serial
|
||||
|
||||
//go:generate errorgen
|
139
infra/conf/shadowsocks.go
Normal file
139
infra/conf/shadowsocks.go
Normal file
@ -0,0 +1,139 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/proxy/shadowsocks"
|
||||
)
|
||||
|
||||
func cipherFromString(c string) shadowsocks.CipherType {
|
||||
switch strings.ToLower(c) {
|
||||
case "aes-256-cfb":
|
||||
return shadowsocks.CipherType_AES_256_CFB
|
||||
case "aes-128-cfb":
|
||||
return shadowsocks.CipherType_AES_128_CFB
|
||||
case "chacha20":
|
||||
return shadowsocks.CipherType_CHACHA20
|
||||
case "chacha20-ietf":
|
||||
return shadowsocks.CipherType_CHACHA20_IETF
|
||||
case "aes-128-gcm", "aead_aes_128_gcm":
|
||||
return shadowsocks.CipherType_AES_128_GCM
|
||||
case "aes-256-gcm", "aead_aes_256_gcm":
|
||||
return shadowsocks.CipherType_AES_256_GCM
|
||||
case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
|
||||
return shadowsocks.CipherType_CHACHA20_POLY1305
|
||||
default:
|
||||
return shadowsocks.CipherType_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
type ShadowsocksServerConfig struct {
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UDP bool `json:"udp"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
OTA *bool `json:"ota"`
|
||||
NetworkList *NetworkList `json:"network"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
||||
config := new(shadowsocks.ServerConfig)
|
||||
config.UdpEnabled = v.UDP
|
||||
config.Network = v.NetworkList.Build()
|
||||
|
||||
if len(v.Password) == 0 {
|
||||
return nil, newError("Shadowsocks password is not specified.")
|
||||
}
|
||||
account := &shadowsocks.Account{
|
||||
Password: v.Password,
|
||||
Ota: shadowsocks.Account_Auto,
|
||||
}
|
||||
if v.OTA != nil {
|
||||
if *v.OTA {
|
||||
account.Ota = shadowsocks.Account_Enabled
|
||||
} else {
|
||||
account.Ota = shadowsocks.Account_Disabled
|
||||
}
|
||||
}
|
||||
account.CipherType = cipherFromString(v.Cipher)
|
||||
if account.CipherType == shadowsocks.CipherType_UNKNOWN {
|
||||
return nil, newError("unknown cipher method: ", v.Cipher)
|
||||
}
|
||||
|
||||
config.User = &protocol.User{
|
||||
Email: v.Email,
|
||||
Level: uint32(v.Level),
|
||||
Account: serial.ToTypedMessage(account),
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type ShadowsocksServerTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Ota bool `json:"ota"`
|
||||
Level byte `json:"level"`
|
||||
}
|
||||
|
||||
type ShadowsocksClientConfig struct {
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
config := new(shadowsocks.ClientConfig)
|
||||
|
||||
if len(v.Servers) == 0 {
|
||||
return nil, newError("0 Shadowsocks server configured.")
|
||||
}
|
||||
|
||||
serverSpecs := make([]*protocol.ServerEndpoint, len(v.Servers))
|
||||
for idx, server := range v.Servers {
|
||||
if server.Address == nil {
|
||||
return nil, newError("Shadowsocks server address is not set.")
|
||||
}
|
||||
if server.Port == 0 {
|
||||
return nil, newError("Invalid Shadowsocks port.")
|
||||
}
|
||||
if len(server.Password) == 0 {
|
||||
return nil, newError("Shadowsocks password is not specified.")
|
||||
}
|
||||
account := &shadowsocks.Account{
|
||||
Password: server.Password,
|
||||
Ota: shadowsocks.Account_Enabled,
|
||||
}
|
||||
if !server.Ota {
|
||||
account.Ota = shadowsocks.Account_Disabled
|
||||
}
|
||||
account.CipherType = cipherFromString(server.Cipher)
|
||||
if account.CipherType == shadowsocks.CipherType_UNKNOWN {
|
||||
return nil, newError("unknown cipher method: ", server.Cipher)
|
||||
}
|
||||
|
||||
ss := &protocol.ServerEndpoint{
|
||||
Address: server.Address.Build(),
|
||||
Port: uint32(server.Port),
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Level: uint32(server.Level),
|
||||
Email: server.Email,
|
||||
Account: serial.ToTypedMessage(account),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
serverSpecs[idx] = ss
|
||||
}
|
||||
|
||||
config.Server = serverSpecs
|
||||
|
||||
return config, nil
|
||||
}
|
36
infra/conf/shadowsocks_test.go
Normal file
36
infra/conf/shadowsocks_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/shadowsocks"
|
||||
)
|
||||
|
||||
func TestShadowsocksServerConfigParsing(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(ShadowsocksServerConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"method": "aes-128-cfb",
|
||||
"password": "v2ray-password"
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &shadowsocks.ServerConfig{
|
||||
User: &protocol.User{
|
||||
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||
CipherType: shadowsocks.CipherType_AES_128_CFB,
|
||||
Password: "v2ray-password",
|
||||
}),
|
||||
},
|
||||
Network: []net.Network{net.Network_TCP},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
99
infra/conf/socks.go
Normal file
99
infra/conf/socks.go
Normal file
@ -0,0 +1,99 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/proxy/socks"
|
||||
)
|
||||
|
||||
type SocksAccount struct {
|
||||
Username string `json:"user"`
|
||||
Password string `json:"pass"`
|
||||
}
|
||||
|
||||
func (v *SocksAccount) Build() *socks.Account {
|
||||
return &socks.Account{
|
||||
Username: v.Username,
|
||||
Password: v.Password,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
AuthMethodNoAuth = "noauth"
|
||||
AuthMethodUserPass = "password"
|
||||
)
|
||||
|
||||
type SocksServerConfig struct {
|
||||
AuthMethod string `json:"auth"`
|
||||
Accounts []*SocksAccount `json:"accounts"`
|
||||
UDP bool `json:"udp"`
|
||||
Host *Address `json:"ip"`
|
||||
Timeout uint32 `json:"timeout"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
}
|
||||
|
||||
func (v *SocksServerConfig) Build() (proto.Message, error) {
|
||||
config := new(socks.ServerConfig)
|
||||
switch v.AuthMethod {
|
||||
case AuthMethodNoAuth:
|
||||
config.AuthType = socks.AuthType_NO_AUTH
|
||||
case AuthMethodUserPass:
|
||||
config.AuthType = socks.AuthType_PASSWORD
|
||||
default:
|
||||
//newError("unknown socks auth method: ", v.AuthMethod, ". Default to noauth.").AtWarning().WriteToLog()
|
||||
config.AuthType = socks.AuthType_NO_AUTH
|
||||
}
|
||||
|
||||
if len(v.Accounts) > 0 {
|
||||
config.Accounts = make(map[string]string, len(v.Accounts))
|
||||
for _, account := range v.Accounts {
|
||||
config.Accounts[account.Username] = account.Password
|
||||
}
|
||||
}
|
||||
|
||||
config.UdpEnabled = v.UDP
|
||||
if v.Host != nil {
|
||||
config.Address = v.Host.Build()
|
||||
}
|
||||
|
||||
config.Timeout = v.Timeout
|
||||
config.UserLevel = v.UserLevel
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type SocksRemoteConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
type SocksClientConfig struct {
|
||||
Servers []*SocksRemoteConfig `json:"servers"`
|
||||
}
|
||||
|
||||
func (v *SocksClientConfig) Build() (proto.Message, error) {
|
||||
config := new(socks.ClientConfig)
|
||||
config.Server = make([]*protocol.ServerEndpoint, len(v.Servers))
|
||||
for idx, serverConfig := range v.Servers {
|
||||
server := &protocol.ServerEndpoint{
|
||||
Address: serverConfig.Address.Build(),
|
||||
Port: uint32(serverConfig.Port),
|
||||
}
|
||||
for _, rawUser := range serverConfig.Users {
|
||||
user := new(protocol.User)
|
||||
if err := json.Unmarshal(rawUser, user); err != nil {
|
||||
return nil, newError("failed to parse Socks user").Base(err).AtError()
|
||||
}
|
||||
account := new(SocksAccount)
|
||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||
return nil, newError("failed to parse socks account").Base(err).AtError()
|
||||
}
|
||||
user.Account = serial.ToTypedMessage(account.Build())
|
||||
server.User = append(server.User, user)
|
||||
}
|
||||
config.Server[idx] = server
|
||||
}
|
||||
return config, nil
|
||||
}
|
92
infra/conf/socks_test.go
Normal file
92
infra/conf/socks_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/socks"
|
||||
)
|
||||
|
||||
func TestSocksInboundConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(SocksServerConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"auth": "password",
|
||||
"accounts": [
|
||||
{
|
||||
"user": "my-username",
|
||||
"pass": "my-password"
|
||||
}
|
||||
],
|
||||
"udp": false,
|
||||
"ip": "127.0.0.1",
|
||||
"timeout": 5,
|
||||
"userLevel": 1
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &socks.ServerConfig{
|
||||
AuthType: socks.AuthType_PASSWORD,
|
||||
Accounts: map[string]string{
|
||||
"my-username": "my-password",
|
||||
},
|
||||
UdpEnabled: false,
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Timeout: 5,
|
||||
UserLevel: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSocksOutboundConfig(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(SocksClientConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"servers": [{
|
||||
"address": "127.0.0.1",
|
||||
"port": 1234,
|
||||
"users": [
|
||||
{"user": "test user", "pass": "test pass", "email": "test@email.com"}
|
||||
]
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &socks.ClientConfig{
|
||||
Server: []*protocol.ServerEndpoint{
|
||||
{
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 1234,
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Email: "test@email.com",
|
||||
Account: serial.ToTypedMessage(&socks.Account{
|
||||
Username: "test user",
|
||||
Password: "test pass",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
89
infra/conf/transport.go
Normal file
89
infra/conf/transport.go
Normal file
@ -0,0 +1,89 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/transport"
|
||||
"v2ray.com/core/transport/internet"
|
||||
)
|
||||
|
||||
type TransportConfig struct {
|
||||
TCPConfig *TCPConfig `json:"tcpSettings"`
|
||||
KCPConfig *KCPConfig `json:"kcpSettings"`
|
||||
WSConfig *WebSocketConfig `json:"wsSettings"`
|
||||
HTTPConfig *HTTPConfig `json:"httpSettings"`
|
||||
DSConfig *DomainSocketConfig `json:"dsSettings"`
|
||||
QUICConfig *QUICConfig `json:"quicSettings"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *TransportConfig) Build() (*transport.Config, error) {
|
||||
config := new(transport.Config)
|
||||
|
||||
if c.TCPConfig != nil {
|
||||
ts, err := c.TCPConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build TCP config").Base(err).AtError()
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "tcp",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
|
||||
if c.KCPConfig != nil {
|
||||
ts, err := c.KCPConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build mKCP config").Base(err).AtError()
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "mkcp",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
|
||||
if c.WSConfig != nil {
|
||||
ts, err := c.WSConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build WebSocket config").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "websocket",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
|
||||
if c.HTTPConfig != nil {
|
||||
ts, err := c.HTTPConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build HTTP config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
|
||||
if c.DSConfig != nil {
|
||||
ds, err := c.DSConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build DomainSocket config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "domainsocket",
|
||||
Settings: serial.ToTypedMessage(ds),
|
||||
})
|
||||
}
|
||||
|
||||
if c.QUICConfig != nil {
|
||||
qs, err := c.QUICConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build QUIC config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "quic",
|
||||
Settings: serial.ToTypedMessage(qs),
|
||||
})
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
223
infra/conf/transport_authenticators.go
Normal file
223
infra/conf/transport_authenticators.go
Normal file
@ -0,0 +1,223 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/transport/internet/headers/http"
|
||||
"v2ray.com/core/transport/internet/headers/noop"
|
||||
"v2ray.com/core/transport/internet/headers/srtp"
|
||||
"v2ray.com/core/transport/internet/headers/tls"
|
||||
"v2ray.com/core/transport/internet/headers/utp"
|
||||
"v2ray.com/core/transport/internet/headers/wechat"
|
||||
"v2ray.com/core/transport/internet/headers/wireguard"
|
||||
)
|
||||
|
||||
type NoOpAuthenticator struct{}
|
||||
|
||||
func (NoOpAuthenticator) Build() (proto.Message, error) {
|
||||
return new(noop.Config), nil
|
||||
}
|
||||
|
||||
type NoOpConnectionAuthenticator struct{}
|
||||
|
||||
func (NoOpConnectionAuthenticator) Build() (proto.Message, error) {
|
||||
return new(noop.ConnectionConfig), nil
|
||||
}
|
||||
|
||||
type SRTPAuthenticator struct{}
|
||||
|
||||
func (SRTPAuthenticator) Build() (proto.Message, error) {
|
||||
return new(srtp.Config), nil
|
||||
}
|
||||
|
||||
type UTPAuthenticator struct{}
|
||||
|
||||
func (UTPAuthenticator) Build() (proto.Message, error) {
|
||||
return new(utp.Config), nil
|
||||
}
|
||||
|
||||
type WechatVideoAuthenticator struct{}
|
||||
|
||||
func (WechatVideoAuthenticator) Build() (proto.Message, error) {
|
||||
return new(wechat.VideoConfig), nil
|
||||
}
|
||||
|
||||
type WireguardAuthenticator struct{}
|
||||
|
||||
func (WireguardAuthenticator) Build() (proto.Message, error) {
|
||||
return new(wireguard.WireguardConfig), nil
|
||||
}
|
||||
|
||||
type DTLSAuthenticator struct{}
|
||||
|
||||
func (DTLSAuthenticator) Build() (proto.Message, error) {
|
||||
return new(tls.PacketConfig), nil
|
||||
}
|
||||
|
||||
type HTTPAuthenticatorRequest struct {
|
||||
Version string `json:"version"`
|
||||
Method string `json:"method"`
|
||||
Path StringList `json:"path"`
|
||||
Headers map[string]*StringList `json:"headers"`
|
||||
}
|
||||
|
||||
func sortMapKeys(m map[string]*StringList) []string {
|
||||
var keys []string
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func (v *HTTPAuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||
config := &http.RequestConfig{
|
||||
Uri: []string{"/"},
|
||||
Header: []*http.Header{
|
||||
{
|
||||
Name: "Host",
|
||||
Value: []string{"www.baidu.com", "www.bing.com"},
|
||||
},
|
||||
{
|
||||
Name: "User-Agent",
|
||||
Value: []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Accept-Encoding",
|
||||
Value: []string{"gzip, deflate"},
|
||||
},
|
||||
{
|
||||
Name: "Connection",
|
||||
Value: []string{"keep-alive"},
|
||||
},
|
||||
{
|
||||
Name: "Pragma",
|
||||
Value: []string{"no-cache"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if len(v.Version) > 0 {
|
||||
config.Version = &http.Version{Value: v.Version}
|
||||
}
|
||||
|
||||
if len(v.Method) > 0 {
|
||||
config.Method = &http.Method{Value: v.Method}
|
||||
}
|
||||
|
||||
if len(v.Path) > 0 {
|
||||
config.Uri = append([]string(nil), (v.Path)...)
|
||||
}
|
||||
|
||||
if len(v.Headers) > 0 {
|
||||
config.Header = make([]*http.Header, 0, len(v.Headers))
|
||||
headerNames := sortMapKeys(v.Headers)
|
||||
for _, key := range headerNames {
|
||||
value := v.Headers[key]
|
||||
if value == nil {
|
||||
return nil, newError("empty HTTP header value: " + key).AtError()
|
||||
}
|
||||
config.Header = append(config.Header, &http.Header{
|
||||
Name: key,
|
||||
Value: append([]string(nil), (*value)...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type HTTPAuthenticatorResponse struct {
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
Headers map[string]*StringList `json:"headers"`
|
||||
}
|
||||
|
||||
func (v *HTTPAuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
||||
config := &http.ResponseConfig{
|
||||
Header: []*http.Header{
|
||||
{
|
||||
Name: "Content-Type",
|
||||
Value: []string{"application/octet-stream", "video/mpeg"},
|
||||
},
|
||||
{
|
||||
Name: "Transfer-Encoding",
|
||||
Value: []string{"chunked"},
|
||||
},
|
||||
{
|
||||
Name: "Connection",
|
||||
Value: []string{"keep-alive"},
|
||||
},
|
||||
{
|
||||
Name: "Pragma",
|
||||
Value: []string{"no-cache"},
|
||||
},
|
||||
{
|
||||
Name: "Cache-Control",
|
||||
Value: []string{"private", "no-cache"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if len(v.Version) > 0 {
|
||||
config.Version = &http.Version{Value: v.Version}
|
||||
}
|
||||
|
||||
if len(v.Status) > 0 || len(v.Reason) > 0 {
|
||||
config.Status = &http.Status{
|
||||
Code: "200",
|
||||
Reason: "OK",
|
||||
}
|
||||
if len(v.Status) > 0 {
|
||||
config.Status.Code = v.Status
|
||||
}
|
||||
if len(v.Reason) > 0 {
|
||||
config.Status.Reason = v.Reason
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.Headers) > 0 {
|
||||
config.Header = make([]*http.Header, 0, len(v.Headers))
|
||||
headerNames := sortMapKeys(v.Headers)
|
||||
for _, key := range headerNames {
|
||||
value := v.Headers[key]
|
||||
if value == nil {
|
||||
return nil, newError("empty HTTP header value: " + key).AtError()
|
||||
}
|
||||
config.Header = append(config.Header, &http.Header{
|
||||
Name: key,
|
||||
Value: append([]string(nil), (*value)...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type HTTPAuthenticator struct {
|
||||
Request HTTPAuthenticatorRequest `json:"request"`
|
||||
Response HTTPAuthenticatorResponse `json:"response"`
|
||||
}
|
||||
|
||||
func (v *HTTPAuthenticator) Build() (proto.Message, error) {
|
||||
config := new(http.Config)
|
||||
requestConfig, err := v.Request.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Request = requestConfig
|
||||
|
||||
responseConfig, err := v.Response.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Response = responseConfig
|
||||
|
||||
return config, nil
|
||||
}
|
476
infra/conf/transport_internet.go
Normal file
476
infra/conf/transport_internet.go
Normal file
@ -0,0 +1,476 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/common/platform/filesystem"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/transport/internet"
|
||||
"v2ray.com/core/transport/internet/domainsocket"
|
||||
"v2ray.com/core/transport/internet/http"
|
||||
"v2ray.com/core/transport/internet/kcp"
|
||||
"v2ray.com/core/transport/internet/quic"
|
||||
"v2ray.com/core/transport/internet/tcp"
|
||||
"v2ray.com/core/transport/internet/tls"
|
||||
"v2ray.com/core/transport/internet/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
kcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoOpAuthenticator) },
|
||||
"srtp": func() interface{} { return new(SRTPAuthenticator) },
|
||||
"utp": func() interface{} { return new(UTPAuthenticator) },
|
||||
"wechat-video": func() interface{} { return new(WechatVideoAuthenticator) },
|
||||
"dtls": func() interface{} { return new(DTLSAuthenticator) },
|
||||
"wireguard": func() interface{} { return new(WireguardAuthenticator) },
|
||||
}, "type", "")
|
||||
|
||||
tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
|
||||
"http": func() interface{} { return new(HTTPAuthenticator) },
|
||||
}, "type", "")
|
||||
)
|
||||
|
||||
type KCPConfig struct {
|
||||
Mtu *uint32 `json:"mtu"`
|
||||
Tti *uint32 `json:"tti"`
|
||||
UpCap *uint32 `json:"uplinkCapacity"`
|
||||
DownCap *uint32 `json:"downlinkCapacity"`
|
||||
Congestion *bool `json:"congestion"`
|
||||
ReadBufferSize *uint32 `json:"readBufferSize"`
|
||||
WriteBufferSize *uint32 `json:"writeBufferSize"`
|
||||
HeaderConfig json.RawMessage `json:"header"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *KCPConfig) Build() (proto.Message, error) {
|
||||
config := new(kcp.Config)
|
||||
|
||||
if c.Mtu != nil {
|
||||
mtu := *c.Mtu
|
||||
if mtu < 576 || mtu > 1460 {
|
||||
return nil, newError("invalid mKCP MTU size: ", mtu).AtError()
|
||||
}
|
||||
config.Mtu = &kcp.MTU{Value: mtu}
|
||||
}
|
||||
if c.Tti != nil {
|
||||
tti := *c.Tti
|
||||
if tti < 10 || tti > 100 {
|
||||
return nil, newError("invalid mKCP TTI: ", tti).AtError()
|
||||
}
|
||||
config.Tti = &kcp.TTI{Value: tti}
|
||||
}
|
||||
if c.UpCap != nil {
|
||||
config.UplinkCapacity = &kcp.UplinkCapacity{Value: *c.UpCap}
|
||||
}
|
||||
if c.DownCap != nil {
|
||||
config.DownlinkCapacity = &kcp.DownlinkCapacity{Value: *c.DownCap}
|
||||
}
|
||||
if c.Congestion != nil {
|
||||
config.Congestion = *c.Congestion
|
||||
}
|
||||
if c.ReadBufferSize != nil {
|
||||
size := *c.ReadBufferSize
|
||||
if size > 0 {
|
||||
config.ReadBuffer = &kcp.ReadBuffer{Size: size * 1024 * 1024}
|
||||
} else {
|
||||
config.ReadBuffer = &kcp.ReadBuffer{Size: 512 * 1024}
|
||||
}
|
||||
}
|
||||
if c.WriteBufferSize != nil {
|
||||
size := *c.WriteBufferSize
|
||||
if size > 0 {
|
||||
config.WriteBuffer = &kcp.WriteBuffer{Size: size * 1024 * 1024}
|
||||
} else {
|
||||
config.WriteBuffer = &kcp.WriteBuffer{Size: 512 * 1024}
|
||||
}
|
||||
}
|
||||
if len(c.HeaderConfig) > 0 {
|
||||
headerConfig, _, err := kcpHeaderLoader.Load(c.HeaderConfig)
|
||||
if err != nil {
|
||||
return nil, newError("invalid mKCP header config.").Base(err).AtError()
|
||||
}
|
||||
ts, err := headerConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, newError("invalid mKCP header config").Base(err).AtError()
|
||||
}
|
||||
config.HeaderConfig = serial.ToTypedMessage(ts)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type TCPConfig struct {
|
||||
HeaderConfig json.RawMessage `json:"header"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *TCPConfig) Build() (proto.Message, error) {
|
||||
config := new(tcp.Config)
|
||||
if len(c.HeaderConfig) > 0 {
|
||||
headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)
|
||||
if err != nil {
|
||||
return nil, newError("invalid TCP header config").Base(err).AtError()
|
||||
}
|
||||
ts, err := headerConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, newError("invalid TCP header config").Base(err).AtError()
|
||||
}
|
||||
config.HeaderSettings = serial.ToTypedMessage(ts)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type WebSocketConfig struct {
|
||||
Path string `json:"path"`
|
||||
Path2 string `json:"Path"` // The key was misspelled. For backward compatibility, we have to keep track the old key.
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *WebSocketConfig) Build() (proto.Message, error) {
|
||||
path := c.Path
|
||||
if len(path) == 0 && len(c.Path2) > 0 {
|
||||
path = c.Path2
|
||||
}
|
||||
header := make([]*websocket.Header, 0, 32)
|
||||
for key, value := range c.Headers {
|
||||
header = append(header, &websocket.Header{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
config := &websocket.Config{
|
||||
Path: path,
|
||||
Header: header,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Host *StringList `json:"host"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func (c *HTTPConfig) Build() (proto.Message, error) {
|
||||
config := &http.Config{
|
||||
Path: c.Path,
|
||||
}
|
||||
if c.Host != nil {
|
||||
config.Host = []string(*c.Host)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type QUICConfig struct {
|
||||
Header json.RawMessage `json:"header"`
|
||||
Security string `json:"security"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (c *QUICConfig) Build() (proto.Message, error) {
|
||||
config := &quic.Config{
|
||||
Key: c.Key,
|
||||
}
|
||||
|
||||
if len(c.Header) > 0 {
|
||||
headerConfig, _, err := kcpHeaderLoader.Load(c.Header)
|
||||
if err != nil {
|
||||
return nil, newError("invalid QUIC header config.").Base(err).AtError()
|
||||
}
|
||||
ts, err := headerConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, newError("invalid QUIC header config").Base(err).AtError()
|
||||
}
|
||||
config.Header = serial.ToTypedMessage(ts)
|
||||
}
|
||||
|
||||
var st protocol.SecurityType
|
||||
switch strings.ToLower(c.Security) {
|
||||
case "aes-128-gcm":
|
||||
st = protocol.SecurityType_AES128_GCM
|
||||
case "chacha20-poly1305":
|
||||
st = protocol.SecurityType_CHACHA20_POLY1305
|
||||
default:
|
||||
st = protocol.SecurityType_NONE
|
||||
}
|
||||
|
||||
config.Security = &protocol.SecurityConfig{
|
||||
Type: st,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type DomainSocketConfig struct {
|
||||
Path string `json:"path"`
|
||||
Abstract bool `json:"abstract"`
|
||||
}
|
||||
|
||||
func (c *DomainSocketConfig) Build() (proto.Message, error) {
|
||||
return &domainsocket.Config{
|
||||
Path: c.Path,
|
||||
Abstract: c.Abstract,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type TLSCertConfig struct {
|
||||
CertFile string `json:"certificateFile"`
|
||||
CertStr []string `json:"certificate"`
|
||||
KeyFile string `json:"keyFile"`
|
||||
KeyStr []string `json:"key"`
|
||||
Usage string `json:"usage"`
|
||||
}
|
||||
|
||||
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||
if len(f) > 0 {
|
||||
return filesystem.ReadFile(f)
|
||||
}
|
||||
if len(s) > 0 {
|
||||
return []byte(strings.Join(s, "\n")), nil
|
||||
}
|
||||
return nil, newError("both file and bytes are empty.")
|
||||
}
|
||||
|
||||
func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||
certificate := new(tls.Certificate)
|
||||
|
||||
cert, err := readFileOrString(c.CertFile, c.CertStr)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse certificate").Base(err)
|
||||
}
|
||||
certificate.Certificate = cert
|
||||
|
||||
if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 {
|
||||
key, err := readFileOrString(c.KeyFile, c.KeyStr)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse key").Base(err)
|
||||
}
|
||||
certificate.Key = key
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.Usage) {
|
||||
case "encipherment":
|
||||
certificate.Usage = tls.Certificate_ENCIPHERMENT
|
||||
case "verify":
|
||||
certificate.Usage = tls.Certificate_AUTHORITY_VERIFY
|
||||
case "issue":
|
||||
certificate.Usage = tls.Certificate_AUTHORITY_ISSUE
|
||||
default:
|
||||
certificate.Usage = tls.Certificate_ENCIPHERMENT
|
||||
}
|
||||
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Insecure bool `json:"allowInsecure"`
|
||||
InsecureCiphers bool `json:"allowInsecureCiphers"`
|
||||
Certs []*TLSCertConfig `json:"certificates"`
|
||||
ServerName string `json:"serverName"`
|
||||
ALPN *StringList `json:"alpn"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config := new(tls.Config)
|
||||
config.Certificate = make([]*tls.Certificate, len(c.Certs))
|
||||
for idx, certConf := range c.Certs {
|
||||
cert, err := certConf.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Certificate[idx] = cert
|
||||
}
|
||||
serverName := c.ServerName
|
||||
config.AllowInsecure = c.Insecure
|
||||
config.AllowInsecureCiphers = c.InsecureCiphers
|
||||
if len(c.ServerName) > 0 {
|
||||
config.ServerName = serverName
|
||||
}
|
||||
if c.ALPN != nil && len(*c.ALPN) > 0 {
|
||||
config.NextProtocol = []string(*c.ALPN)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type TransportProtocol string
|
||||
|
||||
// Build implements Buildable.
|
||||
func (p TransportProtocol) Build() (string, error) {
|
||||
switch strings.ToLower(string(p)) {
|
||||
case "tcp":
|
||||
return "tcp", nil
|
||||
case "kcp", "mkcp":
|
||||
return "mkcp", nil
|
||||
case "ws", "websocket":
|
||||
return "websocket", nil
|
||||
case "h2", "http":
|
||||
return "http", nil
|
||||
case "ds", "domainsocket":
|
||||
return "domainsocket", nil
|
||||
case "quic":
|
||||
return "quic", nil
|
||||
default:
|
||||
return "", newError("Config: unknown transport protocol: ", p)
|
||||
}
|
||||
}
|
||||
|
||||
type SocketConfig struct {
|
||||
Mark int32 `json:"mark"`
|
||||
TFO *bool `json:"tcpFastOpen"`
|
||||
TProxy string `json:"tproxy"`
|
||||
}
|
||||
|
||||
func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||
var tfoSettings internet.SocketConfig_TCPFastOpenState
|
||||
if c.TFO != nil {
|
||||
if *c.TFO {
|
||||
tfoSettings = internet.SocketConfig_Enable
|
||||
} else {
|
||||
tfoSettings = internet.SocketConfig_Disable
|
||||
}
|
||||
}
|
||||
var tproxy internet.SocketConfig_TProxyMode
|
||||
switch strings.ToLower(c.TProxy) {
|
||||
case "tproxy":
|
||||
tproxy = internet.SocketConfig_TProxy
|
||||
case "redirect":
|
||||
tproxy = internet.SocketConfig_Redirect
|
||||
default:
|
||||
tproxy = internet.SocketConfig_Off
|
||||
}
|
||||
|
||||
return &internet.SocketConfig{
|
||||
Mark: c.Mark,
|
||||
Tfo: tfoSettings,
|
||||
Tproxy: tproxy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type StreamConfig struct {
|
||||
Network *TransportProtocol `json:"network"`
|
||||
Security string `json:"security"`
|
||||
TLSSettings *TLSConfig `json:"tlsSettings"`
|
||||
TCPSettings *TCPConfig `json:"tcpSettings"`
|
||||
KCPSettings *KCPConfig `json:"kcpSettings"`
|
||||
WSSettings *WebSocketConfig `json:"wsSettings"`
|
||||
HTTPSettings *HTTPConfig `json:"httpSettings"`
|
||||
DSSettings *DomainSocketConfig `json:"dsSettings"`
|
||||
QUICSettings *QUICConfig `json:"quicSettings"`
|
||||
SocketSettings *SocketConfig `json:"sockopt"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
||||
config := &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
}
|
||||
if c.Network != nil {
|
||||
protocol, err := (*c.Network).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ProtocolName = protocol
|
||||
}
|
||||
if strings.ToLower(c.Security) == "tls" {
|
||||
tlsSettings := c.TLSSettings
|
||||
if tlsSettings == nil {
|
||||
tlsSettings = &TLSConfig{}
|
||||
}
|
||||
ts, err := tlsSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build TLS config.").Base(err)
|
||||
}
|
||||
tm := serial.ToTypedMessage(ts)
|
||||
config.SecuritySettings = append(config.SecuritySettings, tm)
|
||||
config.SecurityType = tm.Type
|
||||
}
|
||||
if c.TCPSettings != nil {
|
||||
ts, err := c.TCPSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build TCP config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "tcp",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
if c.KCPSettings != nil {
|
||||
ts, err := c.KCPSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build mKCP config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "mkcp",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
if c.WSSettings != nil {
|
||||
ts, err := c.WSSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build WebSocket config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "websocket",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
if c.HTTPSettings != nil {
|
||||
ts, err := c.HTTPSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build HTTP config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(ts),
|
||||
})
|
||||
}
|
||||
if c.DSSettings != nil {
|
||||
ds, err := c.DSSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("Failed to build DomainSocket config.").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "domainsocket",
|
||||
Settings: serial.ToTypedMessage(ds),
|
||||
})
|
||||
}
|
||||
if c.QUICSettings != nil {
|
||||
qs, err := c.QUICSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build QUIC config").Base(err)
|
||||
}
|
||||
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||
ProtocolName: "quic",
|
||||
Settings: serial.ToTypedMessage(qs),
|
||||
})
|
||||
}
|
||||
if c.SocketSettings != nil {
|
||||
ss, err := c.SocketSettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build sockopt").Base(err)
|
||||
}
|
||||
config.SocketSettings = ss
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type ProxyConfig struct {
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {
|
||||
if len(v.Tag) == 0 {
|
||||
return nil, newError("Proxy tag is not set.")
|
||||
}
|
||||
return &internet.ProxyConfig{
|
||||
Tag: v.Tag,
|
||||
}, nil
|
||||
}
|
169
infra/conf/transport_test.go
Normal file
169
infra/conf/transport_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/transport"
|
||||
"v2ray.com/core/transport/internet"
|
||||
"v2ray.com/core/transport/internet/headers/http"
|
||||
"v2ray.com/core/transport/internet/headers/noop"
|
||||
"v2ray.com/core/transport/internet/headers/tls"
|
||||
"v2ray.com/core/transport/internet/kcp"
|
||||
"v2ray.com/core/transport/internet/quic"
|
||||
"v2ray.com/core/transport/internet/tcp"
|
||||
"v2ray.com/core/transport/internet/websocket"
|
||||
)
|
||||
|
||||
func TestSocketConfig(t *testing.T) {
|
||||
createParser := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
config := new(SocketConfig)
|
||||
if err := json.Unmarshal([]byte(s), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Build()
|
||||
}
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"mark": 1,
|
||||
"tcpFastOpen": true
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &internet.SocketConfig{
|
||||
Mark: 1,
|
||||
Tfo: internet.SocketConfig_Enable,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransportConfig(t *testing.T) {
|
||||
createParser := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
config := new(TransportConfig)
|
||||
if err := json.Unmarshal([]byte(s), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Build()
|
||||
}
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"tcpSettings": {
|
||||
"header": {
|
||||
"type": "http",
|
||||
"request": {
|
||||
"version": "1.1",
|
||||
"method": "GET",
|
||||
"path": "/b",
|
||||
"headers": {
|
||||
"a": "b",
|
||||
"c": "d"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"version": "1.0",
|
||||
"status": "404",
|
||||
"reason": "Not Found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kcpSettings": {
|
||||
"mtu": 1200,
|
||||
"header": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"wsSettings": {
|
||||
"path": "/t"
|
||||
},
|
||||
"quicSettings": {
|
||||
"key": "abcd",
|
||||
"header": {
|
||||
"type": "dtls"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &transport.Config{
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "tcp",
|
||||
Settings: serial.ToTypedMessage(&tcp.Config{
|
||||
HeaderSettings: serial.ToTypedMessage(&http.Config{
|
||||
Request: &http.RequestConfig{
|
||||
Version: &http.Version{Value: "1.1"},
|
||||
Method: &http.Method{Value: "GET"},
|
||||
Uri: []string{"/b"},
|
||||
Header: []*http.Header{
|
||||
{Name: "a", Value: []string{"b"}},
|
||||
{Name: "c", Value: []string{"d"}},
|
||||
},
|
||||
},
|
||||
Response: &http.ResponseConfig{
|
||||
Version: &http.Version{Value: "1.0"},
|
||||
Status: &http.Status{Code: "404", Reason: "Not Found"},
|
||||
Header: []*http.Header{
|
||||
{
|
||||
Name: "Content-Type",
|
||||
Value: []string{"application/octet-stream", "video/mpeg"},
|
||||
},
|
||||
{
|
||||
Name: "Transfer-Encoding",
|
||||
Value: []string{"chunked"},
|
||||
},
|
||||
{
|
||||
Name: "Connection",
|
||||
Value: []string{"keep-alive"},
|
||||
},
|
||||
{
|
||||
Name: "Pragma",
|
||||
Value: []string{"no-cache"},
|
||||
},
|
||||
{
|
||||
Name: "Cache-Control",
|
||||
Value: []string{"private", "no-cache"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
ProtocolName: "mkcp",
|
||||
Settings: serial.ToTypedMessage(&kcp.Config{
|
||||
Mtu: &kcp.MTU{Value: 1200},
|
||||
HeaderConfig: serial.ToTypedMessage(&noop.Config{}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
ProtocolName: "websocket",
|
||||
Settings: serial.ToTypedMessage(&websocket.Config{
|
||||
Path: "/t",
|
||||
}),
|
||||
},
|
||||
{
|
||||
ProtocolName: "quic",
|
||||
Settings: serial.ToTypedMessage(&quic.Config{
|
||||
Key: "abcd",
|
||||
Security: &protocol.SecurityConfig{
|
||||
Type: protocol.SecurityType_NONE,
|
||||
},
|
||||
Header: serial.ToTypedMessage(&tls.PacketConfig{}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
446
infra/conf/v2ray.go
Normal file
446
infra/conf/v2ray.go
Normal file
@ -0,0 +1,446 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core"
|
||||
"v2ray.com/core/app/dispatcher"
|
||||
"v2ray.com/core/app/proxyman"
|
||||
"v2ray.com/core/app/stats"
|
||||
"v2ray.com/core/common/serial"
|
||||
)
|
||||
|
||||
var (
|
||||
inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"dokodemo-door": func() interface{} { return new(DokodemoConfig) },
|
||||
"http": func() interface{} { return new(HttpServerConfig) },
|
||||
"shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) },
|
||||
"socks": func() interface{} { return new(SocksServerConfig) },
|
||||
"vmess": func() interface{} { return new(VMessInboundConfig) },
|
||||
"mtproto": func() interface{} { return new(MTProtoServerConfig) },
|
||||
}, "protocol", "settings")
|
||||
|
||||
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"blackhole": func() interface{} { return new(BlackholeConfig) },
|
||||
"freedom": func() interface{} { return new(FreedomConfig) },
|
||||
"shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) },
|
||||
"vmess": func() interface{} { return new(VMessOutboundConfig) },
|
||||
"socks": func() interface{} { return new(SocksClientConfig) },
|
||||
"mtproto": func() interface{} { return new(MTProtoClientConfig) },
|
||||
"dns": func() interface{} { return new(DnsOutboundConfig) },
|
||||
}, "protocol", "settings")
|
||||
)
|
||||
|
||||
func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) {
|
||||
kp := make([]proxyman.KnownProtocols, 0, 8)
|
||||
for _, p := range s {
|
||||
switch strings.ToLower(p) {
|
||||
case "http":
|
||||
kp = append(kp, proxyman.KnownProtocols_HTTP)
|
||||
case "https", "tls", "ssl":
|
||||
kp = append(kp, proxyman.KnownProtocols_TLS)
|
||||
default:
|
||||
return nil, newError("Unknown protocol: ", p)
|
||||
}
|
||||
}
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
type SniffingConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DestOverride *StringList `json:"destOverride"`
|
||||
}
|
||||
|
||||
func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
|
||||
var p []string
|
||||
if c.DestOverride != nil {
|
||||
for _, domainOverride := range *c.DestOverride {
|
||||
switch strings.ToLower(domainOverride) {
|
||||
case "http":
|
||||
p = append(p, "http")
|
||||
case "tls", "https", "ssl":
|
||||
p = append(p, "tls")
|
||||
default:
|
||||
return nil, newError("unknown protocol: ", domainOverride)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &proxyman.SniffingConfig{
|
||||
Enabled: c.Enabled,
|
||||
DestinationOverride: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MuxConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Concurrency uint16 `json:"concurrency"`
|
||||
}
|
||||
|
||||
func (c *MuxConfig) GetConcurrency() uint16 {
|
||||
if c.Concurrency == 0 {
|
||||
return 8
|
||||
}
|
||||
return c.Concurrency
|
||||
}
|
||||
|
||||
type InboundDetourAllocationConfig struct {
|
||||
Strategy string `json:"strategy"`
|
||||
Concurrency *uint32 `json:"concurrency"`
|
||||
RefreshMin *uint32 `json:"refresh"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) {
|
||||
config := new(proxyman.AllocationStrategy)
|
||||
switch strings.ToLower(c.Strategy) {
|
||||
case "always":
|
||||
config.Type = proxyman.AllocationStrategy_Always
|
||||
case "random":
|
||||
config.Type = proxyman.AllocationStrategy_Random
|
||||
case "external":
|
||||
config.Type = proxyman.AllocationStrategy_External
|
||||
default:
|
||||
return nil, newError("unknown allocation strategy: ", c.Strategy)
|
||||
}
|
||||
if c.Concurrency != nil {
|
||||
config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
|
||||
Value: *c.Concurrency,
|
||||
}
|
||||
}
|
||||
|
||||
if c.RefreshMin != nil {
|
||||
config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{
|
||||
Value: *c.RefreshMin,
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type InboundDetourConfig struct {
|
||||
Protocol string `json:"protocol"`
|
||||
PortRange *PortRange `json:"port"`
|
||||
ListenOn *Address `json:"listen"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
Tag string `json:"tag"`
|
||||
Allocation *InboundDetourAllocationConfig `json:"allocate"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
DomainOverride *StringList `json:"domainOverride"`
|
||||
SniffingConfig *SniffingConfig `json:"sniffing"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||
receiverSettings := &proxyman.ReceiverConfig{}
|
||||
|
||||
if c.PortRange == nil {
|
||||
return nil, newError("port range not specified in InboundDetour.")
|
||||
}
|
||||
receiverSettings.PortRange = c.PortRange.Build()
|
||||
|
||||
if c.ListenOn != nil {
|
||||
if c.ListenOn.Family().IsDomain() {
|
||||
return nil, newError("unable to listen on domain address: ", c.ListenOn.Domain())
|
||||
}
|
||||
receiverSettings.Listen = c.ListenOn.Build()
|
||||
}
|
||||
if c.Allocation != nil {
|
||||
concurrency := -1
|
||||
if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" {
|
||||
concurrency = int(*c.Allocation.Concurrency)
|
||||
}
|
||||
portRange := int(c.PortRange.To - c.PortRange.From + 1)
|
||||
if concurrency >= 0 && concurrency >= portRange {
|
||||
return nil, newError("not enough ports. concurrency = ", concurrency, " ports: ", c.PortRange.From, " - ", c.PortRange.To)
|
||||
}
|
||||
|
||||
as, err := c.Allocation.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receiverSettings.AllocationStrategy = as
|
||||
}
|
||||
if c.StreamSetting != nil {
|
||||
ss, err := c.StreamSetting.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receiverSettings.StreamSettings = ss
|
||||
}
|
||||
if c.SniffingConfig != nil {
|
||||
s, err := c.SniffingConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to build sniffing config").Base(err)
|
||||
}
|
||||
receiverSettings.SniffingSettings = s
|
||||
}
|
||||
if c.DomainOverride != nil {
|
||||
kp, err := toProtocolList(*c.DomainOverride)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse inbound detour config").Base(err)
|
||||
}
|
||||
receiverSettings.DomainOverride = kp
|
||||
}
|
||||
|
||||
settings := []byte("{}")
|
||||
if c.Settings != nil {
|
||||
settings = ([]byte)(*c.Settings)
|
||||
}
|
||||
rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol)
|
||||
if err != nil {
|
||||
return nil, newError("failed to load inbound detour config.").Base(err)
|
||||
}
|
||||
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
|
||||
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect
|
||||
}
|
||||
ts, err := rawConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &core.InboundHandlerConfig{
|
||||
Tag: c.Tag,
|
||||
ReceiverSettings: serial.ToTypedMessage(receiverSettings),
|
||||
ProxySettings: serial.ToTypedMessage(ts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type OutboundDetourConfig struct {
|
||||
Protocol string `json:"protocol"`
|
||||
SendThrough *Address `json:"sendThrough"`
|
||||
Tag string `json:"tag"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
StreamSetting *StreamConfig `json:"streamSettings"`
|
||||
ProxySettings *ProxyConfig `json:"proxySettings"`
|
||||
MuxSettings *MuxConfig `json:"mux"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
||||
senderSettings := &proxyman.SenderConfig{}
|
||||
|
||||
if c.SendThrough != nil {
|
||||
address := c.SendThrough
|
||||
if address.Family().IsDomain() {
|
||||
return nil, newError("unable to send through: " + address.String())
|
||||
}
|
||||
senderSettings.Via = address.Build()
|
||||
}
|
||||
|
||||
if c.StreamSetting != nil {
|
||||
ss, err := c.StreamSetting.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
senderSettings.StreamSettings = ss
|
||||
}
|
||||
|
||||
if c.ProxySettings != nil {
|
||||
ps, err := c.ProxySettings.Build()
|
||||
if err != nil {
|
||||
return nil, newError("invalid outbound detour proxy settings.").Base(err)
|
||||
}
|
||||
senderSettings.ProxySettings = ps
|
||||
}
|
||||
|
||||
if c.MuxSettings != nil && c.MuxSettings.Enabled {
|
||||
senderSettings.MultiplexSettings = &proxyman.MultiplexingConfig{
|
||||
Enabled: true,
|
||||
Concurrency: uint32(c.MuxSettings.GetConcurrency()),
|
||||
}
|
||||
}
|
||||
|
||||
settings := []byte("{}")
|
||||
if c.Settings != nil {
|
||||
settings = ([]byte)(*c.Settings)
|
||||
}
|
||||
rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol)
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse to outbound detour config.").Base(err)
|
||||
}
|
||||
ts, err := rawConfig.(Buildable).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &core.OutboundHandlerConfig{
|
||||
SenderSettings: serial.ToTypedMessage(senderSettings),
|
||||
Tag: c.Tag,
|
||||
ProxySettings: serial.ToTypedMessage(ts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type StatsConfig struct{}
|
||||
|
||||
func (c *StatsConfig) Build() (*stats.Config, error) {
|
||||
return &stats.Config{}, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Port uint16 `json:"port"` // Port of this Point server. Deprecated.
|
||||
LogConfig *LogConfig `json:"log"`
|
||||
RouterConfig *RouterConfig `json:"routing"`
|
||||
DNSConfig *DnsConfig `json:"dns"`
|
||||
InboundConfigs []InboundDetourConfig `json:"inbounds"`
|
||||
OutboundConfigs []OutboundDetourConfig `json:"outbounds"`
|
||||
InboundConfig *InboundDetourConfig `json:"inbound"` // Deprecated.
|
||||
OutboundConfig *OutboundDetourConfig `json:"outbound"` // Deprecated.
|
||||
InboundDetours []InboundDetourConfig `json:"inboundDetour"` // Deprecated.
|
||||
OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` // Deprecated.
|
||||
Transport *TransportConfig `json:"transport"`
|
||||
Policy *PolicyConfig `json:"policy"`
|
||||
Api *ApiConfig `json:"api"`
|
||||
Stats *StatsConfig `json:"stats"`
|
||||
Reverse *ReverseConfig `json:"reverse"`
|
||||
}
|
||||
|
||||
func applyTransportConfig(s *StreamConfig, t *TransportConfig) {
|
||||
if s.TCPSettings == nil {
|
||||
s.TCPSettings = t.TCPConfig
|
||||
}
|
||||
if s.KCPSettings == nil {
|
||||
s.KCPSettings = t.KCPConfig
|
||||
}
|
||||
if s.WSSettings == nil {
|
||||
s.WSSettings = t.WSConfig
|
||||
}
|
||||
if s.HTTPSettings == nil {
|
||||
s.HTTPSettings = t.HTTPConfig
|
||||
}
|
||||
if s.DSSettings == nil {
|
||||
s.DSSettings = t.DSConfig
|
||||
}
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
func (c *Config) Build() (*core.Config, error) {
|
||||
config := &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||
},
|
||||
}
|
||||
|
||||
if c.Api != nil {
|
||||
apiConf, err := c.Api.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(apiConf))
|
||||
}
|
||||
|
||||
if c.Stats != nil {
|
||||
statsConf, err := c.Stats.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(statsConf))
|
||||
}
|
||||
|
||||
if c.LogConfig != nil {
|
||||
config.App = append(config.App, serial.ToTypedMessage(c.LogConfig.Build()))
|
||||
} else {
|
||||
config.App = append(config.App, serial.ToTypedMessage(DefaultLogConfig()))
|
||||
}
|
||||
|
||||
if c.RouterConfig != nil {
|
||||
routerConfig, err := c.RouterConfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(routerConfig))
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
dnsApp, err := c.DNSConfig.Build()
|
||||
if err != nil {
|
||||
return nil, newError("failed to parse DNS config").Base(err)
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(dnsApp))
|
||||
}
|
||||
|
||||
if c.Policy != nil {
|
||||
pc, err := c.Policy.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(pc))
|
||||
}
|
||||
|
||||
if c.Reverse != nil {
|
||||
r, err := c.Reverse.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||
}
|
||||
|
||||
var inbounds []InboundDetourConfig
|
||||
|
||||
if c.InboundConfig != nil {
|
||||
inbounds = append(inbounds, *c.InboundConfig)
|
||||
}
|
||||
|
||||
if len(c.InboundDetours) > 0 {
|
||||
inbounds = append(inbounds, c.InboundDetours...)
|
||||
}
|
||||
|
||||
if len(c.InboundConfigs) > 0 {
|
||||
inbounds = append(inbounds, c.InboundConfigs...)
|
||||
}
|
||||
|
||||
// Backward compatibility.
|
||||
if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
|
||||
inbounds[0].PortRange = &PortRange{
|
||||
From: uint32(c.Port),
|
||||
To: uint32(c.Port),
|
||||
}
|
||||
}
|
||||
|
||||
for _, rawInboundConfig := range inbounds {
|
||||
if c.Transport != nil {
|
||||
if rawInboundConfig.StreamSetting == nil {
|
||||
rawInboundConfig.StreamSetting = &StreamConfig{}
|
||||
}
|
||||
applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport)
|
||||
}
|
||||
ic, err := rawInboundConfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Inbound = append(config.Inbound, ic)
|
||||
}
|
||||
|
||||
var outbounds []OutboundDetourConfig
|
||||
|
||||
if c.OutboundConfig != nil {
|
||||
outbounds = append(outbounds, *c.OutboundConfig)
|
||||
}
|
||||
|
||||
if len(c.OutboundDetours) > 0 {
|
||||
outbounds = append(outbounds, c.OutboundDetours...)
|
||||
}
|
||||
|
||||
if len(c.OutboundConfigs) > 0 {
|
||||
outbounds = append(outbounds, c.OutboundConfigs...)
|
||||
}
|
||||
|
||||
for _, rawOutboundConfig := range outbounds {
|
||||
if c.Transport != nil {
|
||||
if rawOutboundConfig.StreamSetting == nil {
|
||||
rawOutboundConfig.StreamSetting = &StreamConfig{}
|
||||
}
|
||||
applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport)
|
||||
}
|
||||
oc, err := rawOutboundConfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Outbound = append(config.Outbound, oc)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
338
infra/conf/v2ray_test.go
Normal file
338
infra/conf/v2ray_test.go
Normal file
@ -0,0 +1,338 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core"
|
||||
"v2ray.com/core/app/dispatcher"
|
||||
"v2ray.com/core/app/log"
|
||||
"v2ray.com/core/app/proxyman"
|
||||
"v2ray.com/core/app/router"
|
||||
clog "v2ray.com/core/common/log"
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/blackhole"
|
||||
dns_proxy "v2ray.com/core/proxy/dns"
|
||||
"v2ray.com/core/proxy/freedom"
|
||||
"v2ray.com/core/proxy/vmess"
|
||||
"v2ray.com/core/proxy/vmess/inbound"
|
||||
"v2ray.com/core/transport/internet"
|
||||
"v2ray.com/core/transport/internet/http"
|
||||
"v2ray.com/core/transport/internet/tls"
|
||||
"v2ray.com/core/transport/internet/websocket"
|
||||
)
|
||||
|
||||
func TestV2RayConfig(t *testing.T) {
|
||||
createParser := func() func(string) (proto.Message, error) {
|
||||
return func(s string) (proto.Message, error) {
|
||||
config := new(Config)
|
||||
if err := json.Unmarshal([]byte(s), config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Build()
|
||||
}
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"outbound": {
|
||||
"protocol": "freedom",
|
||||
"settings": {}
|
||||
},
|
||||
"log": {
|
||||
"access": "/var/log/v2ray/access.log",
|
||||
"loglevel": "error",
|
||||
"error": "/var/log/v2ray/error.log"
|
||||
},
|
||||
"inbound": {
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"wsSettings": {
|
||||
"headers": {
|
||||
"host": "example.domain"
|
||||
},
|
||||
"path": ""
|
||||
},
|
||||
"tlsSettings": {
|
||||
"alpn": "h2"
|
||||
},
|
||||
"security": "tls"
|
||||
},
|
||||
"protocol": "vmess",
|
||||
"port": 443,
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"alterId": 100,
|
||||
"security": "aes-128-gcm",
|
||||
"id": "0cdf8a45-303d-4fed-9780-29aa7f54175e"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"inbounds": [{
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"wsSettings": {
|
||||
"headers": {
|
||||
"host": "example.domain"
|
||||
},
|
||||
"path": ""
|
||||
},
|
||||
"tlsSettings": {
|
||||
"alpn": "h2"
|
||||
},
|
||||
"security": "tls"
|
||||
},
|
||||
"protocol": "vmess",
|
||||
"port": "443-500",
|
||||
"allocate": {
|
||||
"strategy": "random",
|
||||
"concurrency": 3
|
||||
},
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"alterId": 100,
|
||||
"security": "aes-128-gcm",
|
||||
"id": "0cdf8a45-303d-4fed-9780-29aa7f54175e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}],
|
||||
"outboundDetour": [
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole"
|
||||
},
|
||||
{
|
||||
"protocol": "dns"
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"strategy": "rules",
|
||||
"settings": {
|
||||
"rules": [
|
||||
{
|
||||
"ip": [
|
||||
"10.0.0.0/8"
|
||||
],
|
||||
"type": "field",
|
||||
"outboundTag": "blocked"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"transport": {
|
||||
"httpSettings": {
|
||||
"path": "/test"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
Parser: createParser(),
|
||||
Output: &core.Config{
|
||||
App: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||
serial.ToTypedMessage(&log.Config{
|
||||
ErrorLogType: log.LogType_File,
|
||||
ErrorLogPath: "/var/log/v2ray/error.log",
|
||||
ErrorLogLevel: clog.Severity_Error,
|
||||
AccessLogType: log.LogType_File,
|
||||
AccessLogPath: "/var/log/v2ray/access.log",
|
||||
}),
|
||||
serial.ToTypedMessage(&router.Config{
|
||||
DomainStrategy: router.Config_AsIs,
|
||||
Rule: []*router.RoutingRule{
|
||||
{
|
||||
Geoip: []*router.GeoIP{
|
||||
{
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{10, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TargetTag: &router.RoutingRule_Tag{
|
||||
Tag: "blocked",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(&http.Config{
|
||||
Path: "/test",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
DomainStrategy: freedom.Config_AS_IS,
|
||||
Timeout: 600,
|
||||
UserLevel: 0,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Tag: "blocked",
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(&http.Config{
|
||||
Path: "/test",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&blackhole.Config{}),
|
||||
},
|
||||
{
|
||||
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "tcp",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(&http.Config{
|
||||
Path: "/test",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}),
|
||||
},
|
||||
},
|
||||
Inbound: []*core.InboundHandlerConfig{
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortRange: &net.PortRange{
|
||||
From: 443,
|
||||
To: 443,
|
||||
},
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "websocket",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "websocket",
|
||||
Settings: serial.ToTypedMessage(&websocket.Config{
|
||||
Header: []*websocket.Header{
|
||||
{
|
||||
Key: "host",
|
||||
Value: "example.domain",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(&http.Config{
|
||||
Path: "/test",
|
||||
}),
|
||||
},
|
||||
},
|
||||
SecurityType: "v2ray.core.transport.internet.tls.Config",
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
NextProtocol: []string{"h2"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Level: 0,
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e",
|
||||
AlterId: 100,
|
||||
SecuritySettings: &protocol.SecurityConfig{
|
||||
Type: protocol.SecurityType_AES128_GCM,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
||||
PortRange: &net.PortRange{
|
||||
From: 443,
|
||||
To: 500,
|
||||
},
|
||||
AllocationStrategy: &proxyman.AllocationStrategy{
|
||||
Type: proxyman.AllocationStrategy_Random,
|
||||
Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
|
||||
Value: 3,
|
||||
},
|
||||
},
|
||||
StreamSettings: &internet.StreamConfig{
|
||||
ProtocolName: "websocket",
|
||||
TransportSettings: []*internet.TransportConfig{
|
||||
{
|
||||
ProtocolName: "websocket",
|
||||
Settings: serial.ToTypedMessage(&websocket.Config{
|
||||
Header: []*websocket.Header{
|
||||
{
|
||||
Key: "host",
|
||||
Value: "example.domain",
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
ProtocolName: "http",
|
||||
Settings: serial.ToTypedMessage(&http.Config{
|
||||
Path: "/test",
|
||||
}),
|
||||
},
|
||||
},
|
||||
SecurityType: "v2ray.core.transport.internet.tls.Config",
|
||||
SecuritySettings: []*serial.TypedMessage{
|
||||
serial.ToTypedMessage(&tls.Config{
|
||||
NextProtocol: []string{"h2"},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Level: 0,
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: "0cdf8a45-303d-4fed-9780-29aa7f54175e",
|
||||
AlterId: 100,
|
||||
SecuritySettings: &protocol.SecurityConfig{
|
||||
Type: protocol.SecurityType_AES128_GCM,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
164
infra/conf/vmess.go
Normal file
164
infra/conf/vmess.go
Normal file
@ -0,0 +1,164 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
"v2ray.com/core/proxy/vmess"
|
||||
"v2ray.com/core/proxy/vmess/inbound"
|
||||
"v2ray.com/core/proxy/vmess/outbound"
|
||||
)
|
||||
|
||||
type VMessAccount struct {
|
||||
ID string `json:"id"`
|
||||
AlterIds uint16 `json:"alterId"`
|
||||
Security string `json:"security"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (a *VMessAccount) Build() *vmess.Account {
|
||||
var st protocol.SecurityType
|
||||
switch strings.ToLower(a.Security) {
|
||||
case "aes-128-gcm":
|
||||
st = protocol.SecurityType_AES128_GCM
|
||||
case "chacha20-poly1305":
|
||||
st = protocol.SecurityType_CHACHA20_POLY1305
|
||||
case "auto":
|
||||
st = protocol.SecurityType_AUTO
|
||||
case "none":
|
||||
st = protocol.SecurityType_NONE
|
||||
default:
|
||||
st = protocol.SecurityType_AUTO
|
||||
}
|
||||
return &vmess.Account{
|
||||
Id: a.ID,
|
||||
AlterId: uint32(a.AlterIds),
|
||||
SecuritySettings: &protocol.SecurityConfig{
|
||||
Type: st,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type VMessDetourConfig struct {
|
||||
ToTag string `json:"to"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessDetourConfig) Build() *inbound.DetourConfig {
|
||||
return &inbound.DetourConfig{
|
||||
To: c.ToTag,
|
||||
}
|
||||
}
|
||||
|
||||
type FeaturesConfig struct {
|
||||
Detour *VMessDetourConfig `json:"detour"`
|
||||
}
|
||||
|
||||
type VMessDefaultConfig struct {
|
||||
AlterIDs uint16 `json:"alterId"`
|
||||
Level byte `json:"level"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {
|
||||
config := new(inbound.DefaultConfig)
|
||||
config.AlterId = uint32(c.AlterIDs)
|
||||
if config.AlterId == 0 {
|
||||
config.AlterId = 32
|
||||
}
|
||||
config.Level = uint32(c.Level)
|
||||
return config
|
||||
}
|
||||
|
||||
type VMessInboundConfig struct {
|
||||
Users []json.RawMessage `json:"clients"`
|
||||
Features *FeaturesConfig `json:"features"`
|
||||
Defaults *VMessDefaultConfig `json:"default"`
|
||||
DetourConfig *VMessDetourConfig `json:"detour"`
|
||||
SecureOnly bool `json:"disableInsecureEncryption"`
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessInboundConfig) Build() (proto.Message, error) {
|
||||
config := &inbound.Config{
|
||||
SecureEncryptionOnly: c.SecureOnly,
|
||||
}
|
||||
|
||||
if c.Defaults != nil {
|
||||
config.Default = c.Defaults.Build()
|
||||
}
|
||||
|
||||
if c.DetourConfig != nil {
|
||||
config.Detour = c.DetourConfig.Build()
|
||||
} else if c.Features != nil && c.Features.Detour != nil {
|
||||
config.Detour = c.Features.Detour.Build()
|
||||
}
|
||||
|
||||
config.User = make([]*protocol.User, len(c.Users))
|
||||
for idx, rawData := range c.Users {
|
||||
user := new(protocol.User)
|
||||
if err := json.Unmarshal(rawData, user); err != nil {
|
||||
return nil, newError("invalid VMess user").Base(err)
|
||||
}
|
||||
account := new(VMessAccount)
|
||||
if err := json.Unmarshal(rawData, account); err != nil {
|
||||
return nil, newError("invalid VMess user").Base(err)
|
||||
}
|
||||
user.Account = serial.ToTypedMessage(account.Build())
|
||||
config.User[idx] = user
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type VMessOutboundTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Users []json.RawMessage `json:"users"`
|
||||
}
|
||||
type VMessOutboundConfig struct {
|
||||
Receivers []*VMessOutboundTarget `json:"vnext"`
|
||||
}
|
||||
|
||||
var bUser = "a06fe789-5ab1-480b-8124-ae4599801ff3"
|
||||
|
||||
// Build implements Buildable
|
||||
func (c *VMessOutboundConfig) Build() (proto.Message, error) {
|
||||
config := new(outbound.Config)
|
||||
|
||||
if len(c.Receivers) == 0 {
|
||||
return nil, newError("0 VMess receiver configured")
|
||||
}
|
||||
serverSpecs := make([]*protocol.ServerEndpoint, len(c.Receivers))
|
||||
for idx, rec := range c.Receivers {
|
||||
if len(rec.Users) == 0 {
|
||||
return nil, newError("0 user configured for VMess outbound")
|
||||
}
|
||||
if rec.Address == nil {
|
||||
return nil, newError("address is not set in VMess outbound config")
|
||||
}
|
||||
spec := &protocol.ServerEndpoint{
|
||||
Address: rec.Address.Build(),
|
||||
Port: uint32(rec.Port),
|
||||
}
|
||||
for _, rawUser := range rec.Users {
|
||||
user := new(protocol.User)
|
||||
if err := json.Unmarshal(rawUser, user); err != nil {
|
||||
return nil, newError("invalid VMess user").Base(err)
|
||||
}
|
||||
account := new(VMessAccount)
|
||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||
return nil, newError("invalid VMess user").Base(err)
|
||||
}
|
||||
user.Account = serial.ToTypedMessage(account.Build())
|
||||
spec.User = append(spec.User, user)
|
||||
}
|
||||
serverSpecs[idx] = spec
|
||||
}
|
||||
config.Receiver = serverSpecs
|
||||
return config, nil
|
||||
}
|
117
infra/conf/vmess_test.go
Normal file
117
infra/conf/vmess_test.go
Normal file
@ -0,0 +1,117 @@
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"v2ray.com/core/common/net"
|
||||
"v2ray.com/core/common/protocol"
|
||||
"v2ray.com/core/common/serial"
|
||||
. "v2ray.com/core/infra/conf"
|
||||
"v2ray.com/core/proxy/vmess"
|
||||
"v2ray.com/core/proxy/vmess/inbound"
|
||||
"v2ray.com/core/proxy/vmess/outbound"
|
||||
)
|
||||
|
||||
func TestVMessOutbound(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(VMessOutboundConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"vnext": [{
|
||||
"address": "127.0.0.1",
|
||||
"port": 80,
|
||||
"users": [
|
||||
{
|
||||
"id": "e641f5ad-9397-41e3-bf1a-e8740dfed019",
|
||||
"email": "love@v2ray.com",
|
||||
"level": 255
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &outbound.Config{
|
||||
Receiver: []*protocol.ServerEndpoint{
|
||||
{
|
||||
Address: &net.IPOrDomain{
|
||||
Address: &net.IPOrDomain_Ip{
|
||||
Ip: []byte{127, 0, 0, 1},
|
||||
},
|
||||
},
|
||||
Port: 80,
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Email: "love@v2ray.com",
|
||||
Level: 255,
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019",
|
||||
AlterId: 0,
|
||||
SecuritySettings: &protocol.SecurityConfig{
|
||||
Type: protocol.SecurityType_AUTO,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestVMessInbound(t *testing.T) {
|
||||
creator := func() Buildable {
|
||||
return new(VMessInboundConfig)
|
||||
}
|
||||
|
||||
runMultiTestCase(t, []TestCase{
|
||||
{
|
||||
Input: `{
|
||||
"clients": [
|
||||
{
|
||||
"id": "27848739-7e62-4138-9fd3-098a63964b6b",
|
||||
"level": 0,
|
||||
"alterId": 16,
|
||||
"email": "love@v2ray.com",
|
||||
"security": "aes-128-gcm"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"level": 0,
|
||||
"alterId": 32
|
||||
},
|
||||
"detour": {
|
||||
"to": "tag_to_detour"
|
||||
},
|
||||
"disableInsecureEncryption": true
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &inbound.Config{
|
||||
User: []*protocol.User{
|
||||
{
|
||||
Level: 0,
|
||||
Email: "love@v2ray.com",
|
||||
Account: serial.ToTypedMessage(&vmess.Account{
|
||||
Id: "27848739-7e62-4138-9fd3-098a63964b6b",
|
||||
AlterId: 16,
|
||||
SecuritySettings: &protocol.SecurityConfig{
|
||||
Type: protocol.SecurityType_AES128_GCM,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
Default: &inbound.DefaultConfig{
|
||||
Level: 0,
|
||||
AlterId: 32,
|
||||
},
|
||||
Detour: &inbound.DetourConfig{
|
||||
To: "tag_to_detour",
|
||||
},
|
||||
SecureEncryptionOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
144
infra/control/api.go
Normal file
144
infra/control/api.go
Normal file
@ -0,0 +1,144 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
logService "v2ray.com/core/app/log/command"
|
||||
statsService "v2ray.com/core/app/stats/command"
|
||||
"v2ray.com/core/common"
|
||||
)
|
||||
|
||||
type ApiCommand struct{}
|
||||
|
||||
func (c *ApiCommand) Name() string {
|
||||
return "api"
|
||||
}
|
||||
|
||||
func (c *ApiCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "Call V2Ray API",
|
||||
Usage: []string{
|
||||
"v2ctl api [--server=127.0.0.1:8080] Service.Method Request",
|
||||
"Call an API in an V2Ray process.",
|
||||
"The following methods are currently supported:",
|
||||
"\tLoggerService.RestartLogger",
|
||||
"\tStatsService.GetStats",
|
||||
"\tStatsService.QueryStats",
|
||||
"Examples:",
|
||||
"v2ctl api --server=127.0.0.1:8080 LoggerService.RestartLogger '' ",
|
||||
"v2ctl api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: \"\" reset: false'",
|
||||
"v2ctl api --server=127.0.0.1:8080 StatsService.GetStats 'name: \"inbound>>>statin>>>traffic>>>downlink\" reset: false'",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiCommand) Execute(args []string) error {
|
||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
|
||||
serverAddrPtr := fs.String("server", "127.0.0.1:8080", "Server address")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(*serverAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
||||
if err != nil {
|
||||
return newError("failed to dial ", *serverAddrPtr).Base(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
unnamedArgs := fs.Args()
|
||||
if len(unnamedArgs) < 2 {
|
||||
return newError("service name or request not specified.")
|
||||
}
|
||||
|
||||
service, method := getServiceMethod(unnamedArgs[0])
|
||||
handler, found := serivceHandlerMap[strings.ToLower(service)]
|
||||
if !found {
|
||||
return newError("unknown service: ", service)
|
||||
}
|
||||
|
||||
response, err := handler(conn, method, unnamedArgs[1])
|
||||
if err != nil {
|
||||
return newError("failed to call service ", unnamedArgs[0]).Base(err)
|
||||
}
|
||||
|
||||
fmt.Println(response)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServiceMethod(s string) (string, string) {
|
||||
ss := strings.Split(s, ".")
|
||||
service := ss[0]
|
||||
var method string
|
||||
if len(ss) > 1 {
|
||||
method = ss[1]
|
||||
}
|
||||
return service, method
|
||||
}
|
||||
|
||||
type serviceHandler func(conn *grpc.ClientConn, method string, request string) (string, error)
|
||||
|
||||
var serivceHandlerMap = map[string]serviceHandler{
|
||||
"statsservice": callStatsService,
|
||||
"loggerservice": callLogService,
|
||||
}
|
||||
|
||||
func callLogService(conn *grpc.ClientConn, method string, request string) (string, error) {
|
||||
client := logService.NewLoggerServiceClient(conn)
|
||||
|
||||
switch strings.ToLower(method) {
|
||||
case "restartlogger":
|
||||
r := &logService.RestartLoggerRequest{}
|
||||
if err := proto.UnmarshalText(request, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.RestartLogger(context.Background(), r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return proto.MarshalTextString(resp), nil
|
||||
default:
|
||||
return "", errors.New("Unknown method: " + method)
|
||||
}
|
||||
}
|
||||
|
||||
func callStatsService(conn *grpc.ClientConn, method string, request string) (string, error) {
|
||||
client := statsService.NewStatsServiceClient(conn)
|
||||
|
||||
switch strings.ToLower(method) {
|
||||
case "getstats":
|
||||
r := &statsService.GetStatsRequest{}
|
||||
if err := proto.UnmarshalText(request, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.GetStats(context.Background(), r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return proto.MarshalTextString(resp), nil
|
||||
case "querystats":
|
||||
r := &statsService.QueryStatsRequest{}
|
||||
if err := proto.UnmarshalText(request, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.QueryStats(context.Background(), r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return proto.MarshalTextString(resp), nil
|
||||
default:
|
||||
return "", errors.New("Unknown method: " + method)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&ApiCommand{}))
|
||||
}
|
139
infra/control/cert.go
Normal file
139
infra/control/cert.go
Normal file
@ -0,0 +1,139 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/protocol/tls/cert"
|
||||
"v2ray.com/core/common/task"
|
||||
)
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (l *stringList) String() string {
|
||||
return "String list"
|
||||
}
|
||||
|
||||
func (l *stringList) Set(v string) error {
|
||||
if len(v) == 0 {
|
||||
return newError("empty value")
|
||||
}
|
||||
*l = append(*l, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
type jsonCert struct {
|
||||
Certificate []string `json:"certificate"`
|
||||
Key []string `json:"key"`
|
||||
}
|
||||
|
||||
type CertificateCommand struct {
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) Name() string {
|
||||
return "cert"
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "Generate TLS certificates.",
|
||||
Usage: []string{
|
||||
"v2ctl cert [--ca] [--domain=v2ray.com] [--expire=240h]",
|
||||
"Generate new TLS certificate",
|
||||
"--ca The new certificate is a CA certificate",
|
||||
"--domain Common name for the certificate",
|
||||
"--exipre Time until certificate expires. 240h = 10 days.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) printJson(certificate *cert.Certificate) {
|
||||
certPEM, keyPEM := certificate.ToPEM()
|
||||
jCert := &jsonCert{
|
||||
Certificate: strings.Split(strings.TrimSpace(string(certPEM)), "\n"),
|
||||
Key: strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
||||
}
|
||||
content, err := json.MarshalIndent(jCert, "", " ")
|
||||
common.Must(err)
|
||||
os.Stdout.Write(content)
|
||||
os.Stdout.WriteString("\n")
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) writeFile(content []byte, name string) error {
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return common.Error2(f.Write(content))
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) printFile(certificate *cert.Certificate, name string) error {
|
||||
certPEM, keyPEM := certificate.ToPEM()
|
||||
return task.Run(context.Background(), func() error {
|
||||
return c.writeFile(certPEM, name+"_cert.pem")
|
||||
}, func() error {
|
||||
return c.writeFile(keyPEM, name+"_key.pem")
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CertificateCommand) Execute(args []string) error {
|
||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
|
||||
var domainNames stringList
|
||||
fs.Var(&domainNames, "domain", "Domain name for the certificate")
|
||||
|
||||
commonName := fs.String("name", "V2Ray Inc", "The common name of this certificate")
|
||||
organization := fs.String("org", "V2Ray Inc", "Organization of the certificate")
|
||||
|
||||
isCA := fs.Bool("ca", false, "Whether this certificate is a CA")
|
||||
jsonOutput := fs.Bool("json", true, "Print certificate in JSON format")
|
||||
fileOutput := fs.String("file", "", "Save certificate in file.")
|
||||
|
||||
expire := fs.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var opts []cert.Option
|
||||
if *isCA {
|
||||
opts = append(opts, cert.Authority(*isCA))
|
||||
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
||||
}
|
||||
|
||||
opts = append(opts, cert.NotAfter(time.Now().Add(*expire)))
|
||||
opts = append(opts, cert.CommonName(*commonName))
|
||||
if len(domainNames) > 0 {
|
||||
opts = append(opts, cert.DNSNames(domainNames...))
|
||||
}
|
||||
opts = append(opts, cert.Organization(*organization))
|
||||
|
||||
cert, err := cert.Generate(nil, opts...)
|
||||
if err != nil {
|
||||
return newError("failed to generate TLS certificate").Base(err)
|
||||
}
|
||||
|
||||
if *jsonOutput {
|
||||
c.printJson(cert)
|
||||
}
|
||||
|
||||
if len(*fileOutput) > 0 {
|
||||
if err := c.printFile(cert, *fileOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&CertificateCommand{}))
|
||||
}
|
51
infra/control/command.go
Normal file
51
infra/control/command.go
Normal file
@ -0,0 +1,51 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Description struct {
|
||||
Short string
|
||||
Usage []string
|
||||
}
|
||||
|
||||
type Command interface {
|
||||
Name() string
|
||||
Description() Description
|
||||
Execute(args []string) error
|
||||
}
|
||||
|
||||
var (
|
||||
commandRegistry = make(map[string]Command)
|
||||
)
|
||||
|
||||
func RegisterCommand(cmd Command) error {
|
||||
entry := strings.ToLower(cmd.Name())
|
||||
if len(entry) == 0 {
|
||||
return newError("empty command name")
|
||||
}
|
||||
commandRegistry[entry] = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCommand(name string) Command {
|
||||
cmd, found := commandRegistry[name]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
type hiddenCommand interface {
|
||||
Hidden() bool
|
||||
}
|
||||
|
||||
func PrintUsage() {
|
||||
for name, cmd := range commandRegistry {
|
||||
if _, ok := cmd.(hiddenCommand); ok {
|
||||
continue
|
||||
}
|
||||
fmt.Println(" ", name, "\t\t\t", cmd.Description())
|
||||
}
|
||||
}
|
3
infra/control/control.go
Normal file
3
infra/control/control.go
Normal file
@ -0,0 +1,3 @@
|
||||
package control
|
||||
|
||||
//go:generate errorgen
|
9
infra/control/errors.generated.go
Normal file
9
infra/control/errors.generated.go
Normal file
@ -0,0 +1,9 @@
|
||||
package control
|
||||
|
||||
import "v2ray.com/core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
70
infra/control/fetch.go
Normal file
70
infra/control/fetch.go
Normal file
@ -0,0 +1,70 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/buf"
|
||||
)
|
||||
|
||||
type FetchCommand struct{}
|
||||
|
||||
func (c *FetchCommand) Name() string {
|
||||
return "fetch"
|
||||
}
|
||||
|
||||
func (c *FetchCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "Fetch resources",
|
||||
Usage: []string{"v2ctl fetch <url>"},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FetchCommand) isValidScheme(scheme string) bool {
|
||||
scheme = strings.ToLower(scheme)
|
||||
return scheme == "http" || scheme == "https"
|
||||
}
|
||||
|
||||
func (c *FetchCommand) Execute(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return newError("empty url")
|
||||
}
|
||||
target := args[0]
|
||||
parsedTarget, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return newError("invalid URL: ", target).Base(err)
|
||||
}
|
||||
if !c.isValidScheme(parsedTarget.Scheme) {
|
||||
return newError("invalid scheme: ", parsedTarget.Scheme)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: parsedTarget,
|
||||
Close: true,
|
||||
})
|
||||
if err != nil {
|
||||
return newError("failed to dial to ", target).Base(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return newError("unexpected HTTP status code: ", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := buf.ReadAllToBytes(resp.Body)
|
||||
if err != nil {
|
||||
return newError("failed to read HTTP response").Base(err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(content)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&FetchCommand{}))
|
||||
}
|
53
infra/control/love.go
Normal file
53
infra/control/love.go
Normal file
@ -0,0 +1,53 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/platform"
|
||||
)
|
||||
|
||||
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=="
|
||||
|
||||
type LoveCommand struct{}
|
||||
|
||||
func (*LoveCommand) Name() string {
|
||||
return "lovevictoria"
|
||||
}
|
||||
|
||||
func (*LoveCommand) Hidden() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *LoveCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "",
|
||||
Usage: []string{""},
|
||||
}
|
||||
}
|
||||
|
||||
func (*LoveCommand) Execute([]string) error {
|
||||
c, err := base64.StdEncoding.DecodeString(content)
|
||||
common.Must(err)
|
||||
reader, err := gzip.NewReader(bytes.NewBuffer(c))
|
||||
common.Must(err)
|
||||
b := make([]byte, 4096)
|
||||
nBytes, _ := reader.Read(b)
|
||||
|
||||
bb := bytes.NewBuffer(b[:nBytes])
|
||||
scanner := bufio.NewScanner(bb)
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
fmt.Print(s + platform.LineSeparator())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&LoveCommand{}))
|
||||
}
|
8
infra/control/main/BUILD
Normal file
8
infra/control/main/BUILD
Normal file
@ -0,0 +1,8 @@
|
||||
load("//bazel:build.bzl", "foreign_go_binary")
|
||||
load("//bazel:gpg.bzl", "gpg_sign")
|
||||
load("//bazel:matrix.bzl", "SUPPORTED_MATRIX")
|
||||
load("//tools/control/main:targets.bzl", "gen_targets")
|
||||
|
||||
package(default_visibility=["//visibility:public"])
|
||||
|
||||
gen_targets(SUPPORTED_MATRIX)
|
48
infra/control/main/main.go
Normal file
48
infra/control/main/main.go
Normal file
@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "v2ray.com/core/infra/conf/command"
|
||||
"v2ray.com/core/infra/control"
|
||||
)
|
||||
|
||||
func getCommandName() string {
|
||||
if len(os.Args) > 1 {
|
||||
return os.Args[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
name := getCommandName()
|
||||
cmd := control.GetCommand(name)
|
||||
if cmd == nil {
|
||||
fmt.Fprintln(os.Stderr, "Unknown command:", name)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
|
||||
fmt.Println("v2ctl <command>")
|
||||
fmt.Println("Available commands:")
|
||||
control.PrintUsage()
|
||||
return
|
||||
}
|
||||
|
||||
if err := cmd.Execute(os.Args[2:]); err != nil {
|
||||
hasError := false
|
||||
if err != flag.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
fmt.Fprintln(os.Stderr)
|
||||
hasError = true
|
||||
}
|
||||
|
||||
for _, line := range cmd.Description().Usage {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
if hasError {
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
56
infra/control/main/targets.bzl
Normal file
56
infra/control/main/targets.bzl
Normal file
@ -0,0 +1,56 @@
|
||||
load("//bazel:build.bzl", "foreign_go_binary")
|
||||
load("//bazel:gpg.bzl", "gpg_sign")
|
||||
|
||||
def gen_targets(matrix):
|
||||
output = "v2ctl"
|
||||
pkg = "v2ray.com/core/infra/control/main"
|
||||
|
||||
for (os, arch) in matrix:
|
||||
bin_name = "v2ctl_" + os + "_" + arch
|
||||
foreign_go_binary(
|
||||
name = bin_name,
|
||||
pkg = pkg,
|
||||
output = output,
|
||||
os = os,
|
||||
arch = arch,
|
||||
gotags = "confonly",
|
||||
)
|
||||
|
||||
gpg_sign(
|
||||
name = bin_name + "_sig",
|
||||
base = ":" + bin_name,
|
||||
)
|
||||
|
||||
if arch in ["mips", "mipsle"]:
|
||||
bin_name = "v2ctl_" + os + "_" + arch + "_softfloat"
|
||||
foreign_go_binary(
|
||||
name = bin_name,
|
||||
pkg = pkg,
|
||||
output = output + "_softfloat",
|
||||
os = os,
|
||||
arch = arch,
|
||||
mips = "softfloat",
|
||||
gotags = "confonly",
|
||||
)
|
||||
|
||||
gpg_sign(
|
||||
name = bin_name + "_sig",
|
||||
base = ":" + bin_name,
|
||||
)
|
||||
|
||||
if arch in ["arm"]:
|
||||
bin_name = "v2ctl_" + os + "_" + arch + "_armv7"
|
||||
foreign_go_binary(
|
||||
name = bin_name,
|
||||
pkg = pkg,
|
||||
output = output + "_armv7",
|
||||
os = os,
|
||||
arch = arch,
|
||||
arm = "7",
|
||||
gotags = "confonly",
|
||||
)
|
||||
|
||||
gpg_sign(
|
||||
name = bin_name + "_sig",
|
||||
base = ":" + bin_name,
|
||||
)
|
31
infra/control/uuid.go
Normal file
31
infra/control/uuid.go
Normal file
@ -0,0 +1,31 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/core/common/uuid"
|
||||
)
|
||||
|
||||
type UUIDCommand struct{}
|
||||
|
||||
func (c *UUIDCommand) Name() string {
|
||||
return "uuid"
|
||||
}
|
||||
|
||||
func (c *UUIDCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "Generate new UUIDs",
|
||||
Usage: []string{"v2ctl uuid"},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UUIDCommand) Execute([]string) error {
|
||||
u := uuid.New()
|
||||
fmt.Println(u.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&UUIDCommand{}))
|
||||
}
|
137
infra/control/verify.go
Normal file
137
infra/control/verify.go
Normal file
@ -0,0 +1,137 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
)
|
||||
|
||||
const (
|
||||
pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: GPGTools - https://gpgtools.org
|
||||
|
||||
mQINBFiuFLcBEACtu5pycj7nHINq9gdkWtQhOdQPMRmbWPbCfxBRceIyB9IHUKay
|
||||
ldKEAA5DlOtub2ao811pLqcvcWMN61vmwDE9wcBBf1BRpoTb1XB4k60UDuCH4m9u
|
||||
r/XcwGaVBchiO8mdqCpB/h0rGXuoJ2Lqk4kXmyRZuaX2WUg7eOK9ZfslaaBc8lvI
|
||||
r5UvY7UL39LtzvOhQ+el2fXhktwZnCjDlovZzRVpn0QXXUAnuDuzCmd04NXjHZZB
|
||||
8q+h7jZrPrNusPzThkcaTUyuMqAHSrn0plNV1Ne0gDsUjGIOEoWtodnTeYGjkodu
|
||||
qipmLoFiFz0MsdD6CBs6LOr2OIjqJ8TtiMj2MqPiKZTVOb+hpmH1Cs6EN3IhCiLX
|
||||
QbiKX3UjBdVRIFlr4sL/JvOpLKr1RaEQS3nJ2m/Xuki1AOeKLoX8ebPca34tyXj0
|
||||
2gs7Khmfa02TI+fvcAlwzfwhDDab96SnKNOK6XDp0rh3ZTKVYeFhcN7m9z8FWHyJ
|
||||
O1onRVaq2bsKPX1Zv9ZC7jZIAMV2pC26UmRc7nJ/xdFj3tafA5hvILUifpO1qdlX
|
||||
iOCK+biPU3T9c6FakNiQ0sXAqhHbKaJNYcjDF3H3QIs1a35P7kfUJ+9Nc1WoCFGV
|
||||
Gh94dVLMGuoh+qo0A0qCg/y0/gGeZQ7G3jT5NXFx6UjlAb42R/dP+VSg6QARAQAB
|
||||
tCVPZmZpY2lhbCBSZWxlYXNlIDxvZmZpY2lhbEB2MnJheS5jb20+iQJUBBMBCgA+
|
||||
AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAFiEEiwxeMlNgMveaPc7Z4a+lUMfT
|
||||
xJoFAlqRYBMFCQPF0FwACgkQ4a+lUMfTxJoymBAAnyqLfEdmP0ulki3uCQvIH4JD
|
||||
OXvFRyTLYweLehGqZ63i7yy0c1BzOsQbmQy2Trl2uiCgjOLmA6LdFB2d3rhsFssK
|
||||
fhFGroqCOHPdG7thSnBu9C0ohWdoiE1hfXVUtRn0P2vfqswNMdxwNwlZiRhWJemw
|
||||
1WmlaSXRp3PznC1eCYwUaS5IT18rzJyuk8z/Scb9DEWQwPhypz+NTE3j7qvQFmdP
|
||||
0cEDGUUXVe3HQ7oHlC+hzL79KttJeEMl575YbuLtAeRSJC0M+IgXd8YKuoORhqFM
|
||||
OwW4CNVMnAiF6mmb2Wf1hM+A9ydWVd3rz7sp3k1n4i5Hl4ftEz2cdicTX1JBG4ZB
|
||||
wsa9pfC5jk+negIQVvHPQRtWc/2bNYxNBF2cIpKF9wQ00E/wP64vl5QwJzs58Fqc
|
||||
cl3AwfskfvzeLSpdKlOCLE8FSQiKQ/NNw9fAuAe7YxW9xSKRTFGx8yQCNd11fmFe
|
||||
iMCDsBE9I51yUy8ywEtnedHi6mxMrnLv24VkD7jQZBWlvMDUEhGy2f6KgrSHTdEJ
|
||||
ZchSxfEIaM9Thy1E/3f6dQVkiPsf+/4wikS6sCdyW+ITVYc6yE5MvRz5oDjQH4z5
|
||||
JoELeuNxR59kpBErgdr8DBHSJNuxIT63QynrglwsG319Dzu5uPUC6WfqUGG9ATJ0
|
||||
neWkINHrf53bVk2rUG65Ag0EWK4UtwEQAL+11tnPaWlnbVj64j1Qikd+2gZRR7XF
|
||||
fNx1RHHHr4fyxmXUteZFS/L7QHJMDUYmVe6yiq6cvafuygqaUdrp8FLqapGZrsnj
|
||||
jH4i+h1cnZBiO4ui3mA/oaQM/FVjQDQ1LBeLlVxGDYhj/mlmDfYOIsd0wys0AmmW
|
||||
ytPsx0xXnbd9lkJpItfilAR+p7rbHc+755ZIIXPCOH1bXfJz+x0yafi7TgQgEC/M
|
||||
a4SeXVSpygKamZxYbdTpV355Fa4FHCAcK8v3+LnhE6c/4HXnGiuCAO3Lm1ZhgT3E
|
||||
xr8TjlWqdUFJiMmCAf9x8UidBoa6UGyW/yI55CbH35f5p3Tgq0k4Sjq8OrwC6qJm
|
||||
WGWv0HTCs9m21ie3yDKZljVfZ+gXSkaY84JbcYbmAEXH42Y/fEQdkhxxVELHt6Tk
|
||||
1bYvpW1NgRopw9U/mV8mERc0H6Vp+KoWU4uXiHK532YR4kUmvWh5WiSPFu/e6t5+
|
||||
/iWVwXVzvrDWx76cKuye1PgF/CmhKLc1JacJgaEtxuXvVXI4er+aTL/HbiISdzfc
|
||||
tYYdEVSYlkjJdV3/30HsupdsV/Y7O2DiGhlsGa5pKXVLmAvvHzdDfc2iKIbRSRWR
|
||||
kHni7uw/r/ZY78j5yBxwjZkopo3A5NJhByBOnNh9ZaWHBrc1a3WSsItGAn5ORHWk
|
||||
Q1KJY7SDFcXvABEBAAGJAiUEGAEKAA8FAliuFLcCGwwFCQHhM4AACgkQ4a+lUMfT
|
||||
xJrRCA//clpNxJahlPqEsdzCyYEGeXvI1dcZvUmEg+Nm6n1ohRVw1lqP+JbS31N4
|
||||
lByA93R2S5QVjMdr9KranXLC4F+bCJak5wbk7Pza9jqejf5f9nSqwc+M3EkMI2Sy
|
||||
2UlokDwK8m3yMtCf3vRDifvMGXpdUVsWreYvhY5owZfgYD1Ojy6toYqE31HGJEBM
|
||||
z+nGGKkAHVKOZbQAY9X6yAxGYuoV1Z2vddu7OJ4IMdqC4mxbndmKhsfGvotNVgFT
|
||||
WRW9DsKP+Im4WrNpcF7hxZFKNMlw3RbvrrFkCVYuejLUY9xEb57gqLT2APo0LmtX
|
||||
XfvJVB3X2uOelu/MAnnANmPg4Ej8J7D/Q+XX33IGXCrVXo0CVEPscFSqn6O94Ni8
|
||||
INpICE6G1EW/y+iZWcmjx59AnKYeFa40xgr/7TYZmouGBXfBNhtsghFlZY7Hw7ZD
|
||||
Ton1Wxcv14DPigiItYk7WkOiyPTLpAloWRSzs7GDFi2MQaFnrrrJ3ep0wHKuaaYl
|
||||
KJh08QdpalNSjGiga6boN1MH5FkI2NYAyGwQGvvcMe+TDEK43KcH4AssiZNtuXzx
|
||||
fkXkose778mzGzk5rBr0jGtKAxV2159CaI2KzR+uN7JwzoHrRRhVu/OWcaL/5MKq
|
||||
OUUihc22Z9/8GnKH1gscBhoIF+cqqOfzTIA6KrJHIC2u5Vpjvac=
|
||||
=xv/V
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
)
|
||||
|
||||
func firstIdentity(m map[string]*openpgp.Identity) string {
|
||||
for k := range m {
|
||||
return k
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type VerifyCommand struct{}
|
||||
|
||||
func (c *VerifyCommand) Name() string {
|
||||
return "verify"
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) Description() Description {
|
||||
return Description{
|
||||
Short: "Verify if a binary is officially signed.",
|
||||
Usage: []string{
|
||||
"v2ctl verify [--sig=<sig-file>] file",
|
||||
"Verify the file officially signed by V2Ray.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VerifyCommand) Execute(args []string) error {
|
||||
fs := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
|
||||
sigFile := fs.String("sig", "", "Path to the signature file")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target := fs.Arg(0)
|
||||
if len(target) == 0 {
|
||||
return newError("empty file path.")
|
||||
}
|
||||
|
||||
if len(*sigFile) == 0 {
|
||||
*sigFile = target + ".sig"
|
||||
}
|
||||
|
||||
targetReader, err := os.Open(os.ExpandEnv(target))
|
||||
if err != nil {
|
||||
return newError("failed to open file: ", target).Base(err)
|
||||
}
|
||||
|
||||
sigReader, err := os.Open(os.ExpandEnv(*sigFile))
|
||||
if err != nil {
|
||||
return newError("failed to open file ", *sigFile).Base(err)
|
||||
}
|
||||
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pubkey))
|
||||
if err != nil {
|
||||
return newError("failed to create keyring").Base(err)
|
||||
}
|
||||
|
||||
entity, err := openpgp.CheckDetachedSignature(keyring, targetReader, sigReader)
|
||||
if err != nil {
|
||||
return newError("failed to verify signature").Base(err)
|
||||
}
|
||||
|
||||
fmt.Println("Signed by:", firstIdentity(entity.Identities))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(RegisterCommand(&VerifyCommand{}))
|
||||
}
|
95
infra/vprotogen/main.go
Normal file
95
infra/vprotogen/main.go
Normal file
@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"v2ray.com/core/common"
|
||||
)
|
||||
|
||||
var protocMap = map[string]string{
|
||||
"windows": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "windows", "protoc.exe"),
|
||||
"darwin": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "macos", "protoc"),
|
||||
"linux": filepath.Join(os.Getenv("GOPATH"), "src", "v2ray.com", "core", ".dev", "protoc", "linux", "protoc"),
|
||||
}
|
||||
|
||||
var (
|
||||
repo = flag.String("repo", "", "Repo for protobuf generation, such as v2ray.com/core")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
protofiles := make(map[string][]string)
|
||||
protoc := protocMap[runtime.GOOS]
|
||||
gosrc := filepath.Join(os.Getenv("GOPATH"), "src")
|
||||
reporoot := filepath.Join(os.Getenv("GOPATH"), "src", *repo)
|
||||
|
||||
filepath.Walk(reporoot, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
filename := filepath.Base(path)
|
||||
if strings.HasSuffix(filename, ".proto") {
|
||||
protofiles[dir] = append(protofiles[dir], path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, files := range protofiles {
|
||||
args := []string{"--proto_path", gosrc, "--go_out", "plugins=grpc:" + gosrc}
|
||||
args = append(args, files...)
|
||||
cmd := exec.Command(protoc, args...)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
common.Must(filepath.Walk(reporoot, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(info.Name(), ".pb.go") {
|
||||
return nil
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content = bytes.Replace(content, []byte("\"golang.org/x/net/context\""), []byte("\"context\""), 1)
|
||||
|
||||
pos := bytes.Index(content, []byte("\npackage"))
|
||||
if pos > 0 {
|
||||
content = content[pos+1:]
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(path, content, info.Mode())
|
||||
}))
|
||||
}
|
@ -3,7 +3,7 @@ package jsonem
|
||||
import (
|
||||
"v2ray.com/core"
|
||||
"v2ray.com/core/common"
|
||||
"v2ray.com/ext/tools/conf/serial"
|
||||
"v2ray.com/core/infra/conf/serial"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
2
proto.go
2
proto.go
@ -2,5 +2,5 @@ package core
|
||||
|
||||
//go:generate go get -u "github.com/golang/protobuf/protoc-gen-go"
|
||||
//go:generate go get -u "github.com/golang/protobuf/proto"
|
||||
//go:generate go install "v2ray.com/ext/tools/vprotogen"
|
||||
//go:generate go install "v2ray.com/core/infra/vprotogen"
|
||||
//go:generate vprotogen -repo v2ray.com/core
|
||||
|
Loading…
Reference in New Issue
Block a user