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