1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-06-22 23:45:24 +00:00

merge ext into core

This commit is contained in:
Darien Raymond 2019-02-10 19:04:11 +01:00
parent d84166ba35
commit 4eb2b5e607
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
71 changed files with 6346 additions and 18 deletions

View File

@ -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)

View File

@ -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
{

View 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
View File

@ -0,0 +1,5 @@
filegroup(
name = "rules",
srcs = glob(["*.bzl"]),
visibility = ["//visibility:public"],
)

66
infra/bazel/build.bzl Normal file
View 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
View 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
View 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
View 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
View 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
View 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",
"")
)

View 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
View File

@ -0,0 +1,7 @@
package conf
import "github.com/golang/protobuf/proto"
type Buildable interface {
Build() (proto.Message, error)
}

View 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{}))
}

View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
package conf
//go:generate errorgen

176
infra/conf/dns.go Normal file
View 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
View 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
View 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
View 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
}

View 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,
},
},
})
}

View 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
View 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
}

View 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,
},
},
})
}

View 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
View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
}

View 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
View 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
View 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",
},
},
},
},
},
})
}

View 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{})
}

View 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
}

View 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)
}
}
}

View File

@ -0,0 +1,3 @@
package serial
//go:generate errorgen

139
infra/conf/shadowsocks.go Normal file
View 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
}

View 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
View 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
View 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
View 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
}

View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
package control
//go:generate errorgen

View 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
View 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
View 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
View 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)

View 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)
}
}
}

View 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
View 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
View 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
View 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())
}))
}

View File

@ -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() {

View File

@ -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