mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
164
main/commands/all/api.go
Normal file
164
main/commands/all/api.go
Normal file
|
@ -0,0 +1,164 @@
|
|||
package all
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
logService "github.com/xtls/xray-core/v1/app/log/command"
|
||||
statsService "github.com/xtls/xray-core/v1/app/stats/command"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
// cmdAPI calls an API in an Xray process
|
||||
var cmdAPI = &base.Command{
|
||||
UsageLine: "{{.Exec}} api [-server 127.0.0.1:8080] <action> <parameter>",
|
||||
Short: "Call an API in a Xray process",
|
||||
Long: `
|
||||
Call an API in a Xray process, API calls in this command have a timeout to the server of 3 seconds.
|
||||
|
||||
The following methods are currently supported:
|
||||
|
||||
LoggerService.RestartLogger
|
||||
StatsService.GetStats
|
||||
StatsService.QueryStats
|
||||
|
||||
Examples:
|
||||
|
||||
{{.Exec}} api --server=127.0.0.1:8080 LoggerService.RestartLogger ''
|
||||
{{.Exec}} api --server=127.0.0.1:8080 StatsService.QueryStats 'pattern: "" reset: false'
|
||||
{{.Exec}} api --server=127.0.0.1:8080 StatsService.GetStats 'name: "inbound>>>statin>>>traffic>>>downlink" reset: false'
|
||||
{{.Exec}} api --server=127.0.0.1:8080 StatsService.GetSysStats ''
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdAPI.Run = executeAPI // break init loop
|
||||
}
|
||||
|
||||
var (
|
||||
apiServerAddrPtr = cmdAPI.Flag.String("server", "127.0.0.1:8080", "")
|
||||
)
|
||||
|
||||
func executeAPI(cmd *base.Command, args []string) {
|
||||
unnamedArgs := cmdAPI.Flag.Args()
|
||||
if len(unnamedArgs) < 2 {
|
||||
base.Fatalf("service name or request not specified.")
|
||||
}
|
||||
|
||||
service, method := getServiceMethod(unnamedArgs[0])
|
||||
handler, found := serivceHandlerMap[strings.ToLower(service)]
|
||||
if !found {
|
||||
base.Fatalf("unknown service: %s", service)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := grpc.DialContext(ctx, *apiServerAddrPtr, grpc.WithInsecure(), grpc.WithBlock())
|
||||
if err != nil {
|
||||
base.Fatalf("failed to dial %s", *apiServerAddrPtr)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
response, err := handler(ctx, conn, method, unnamedArgs[1])
|
||||
if err != nil {
|
||||
base.Fatalf("failed to call service %s", unnamedArgs[0])
|
||||
}
|
||||
|
||||
fmt.Println(response)
|
||||
}
|
||||
|
||||
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(ctx context.Context, conn *grpc.ClientConn, method string, request string) (string, error)
|
||||
|
||||
var serivceHandlerMap = map[string]serviceHandler{
|
||||
"statsservice": callStatsService,
|
||||
"loggerservice": callLogService,
|
||||
}
|
||||
|
||||
func callLogService(ctx context.Context, 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.Unmarshal([]byte(request), r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.RestartLogger(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(m), nil
|
||||
default:
|
||||
return "", errors.New("Unknown method: " + method)
|
||||
}
|
||||
}
|
||||
|
||||
func callStatsService(ctx context.Context, 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.Unmarshal([]byte(request), r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.GetStats(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(m), nil
|
||||
case "querystats":
|
||||
r := &statsService.QueryStatsRequest{}
|
||||
if err := proto.Unmarshal([]byte(request), r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := client.QueryStats(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(m), nil
|
||||
case "getsysstats":
|
||||
// SysStatsRequest is an empty message
|
||||
r := &statsService.SysStatsRequest{}
|
||||
resp, err := client.GetSysStats(ctx, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(m), nil
|
||||
default:
|
||||
return "", errors.New("Unknown method: " + method)
|
||||
}
|
||||
}
|
15
main/commands/all/commands.go
Normal file
15
main/commands/all/commands.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package all
|
||||
|
||||
import "github.com/xtls/xray-core/v1/main/commands/base"
|
||||
|
||||
// go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
||||
|
||||
func init() {
|
||||
base.RootCommand.Commands = append(
|
||||
base.RootCommand.Commands,
|
||||
cmdAPI,
|
||||
cmdConvert,
|
||||
cmdTLS,
|
||||
cmdUUID,
|
||||
)
|
||||
}
|
126
main/commands/all/convert.go
Normal file
126
main/commands/all/convert.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package all
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/infra/conf"
|
||||
"github.com/xtls/xray-core/v1/infra/conf/serial"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var cmdConvert = &base.Command{
|
||||
UsageLine: "{{.Exec}} convert [json file] [json file] ...",
|
||||
Short: "Convert multiple json config to protobuf",
|
||||
Long: `
|
||||
Convert multiple json config to protobuf.
|
||||
|
||||
Examples:
|
||||
|
||||
{{.Exec}} convert config.json c1.json c2.json <url>.json
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdConvert.Run = executeConvert // break init loop
|
||||
}
|
||||
|
||||
func executeConvert(cmd *base.Command, args []string) {
|
||||
unnamedArgs := cmdConvert.Flag.Args()
|
||||
if len(unnamedArgs) < 1 {
|
||||
base.Fatalf("empty config list")
|
||||
}
|
||||
|
||||
conf := &conf.Config{}
|
||||
for _, arg := range unnamedArgs {
|
||||
fmt.Fprintf(os.Stderr, "Read config: %s", arg)
|
||||
r, err := loadArg(arg)
|
||||
common.Must(err)
|
||||
c, err := serial.DecodeJSONConfig(r)
|
||||
if err != nil {
|
||||
base.Fatalf(err.Error())
|
||||
}
|
||||
conf.Override(c, arg)
|
||||
}
|
||||
|
||||
pbConfig, err := conf.Build()
|
||||
if err != nil {
|
||||
base.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
bytesConfig, err := proto.Marshal(pbConfig)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to marshal proto config: %s", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(bytesConfig); err != nil {
|
||||
base.Fatalf("failed to write proto config: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// loadArg loads one arg, maybe an remote url, or local file path
|
||||
func loadArg(arg string) (out io.Reader, err error) {
|
||||
var data []byte
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||
data, err = FetchHTTPContent(arg)
|
||||
|
||||
case arg == "stdin:":
|
||||
data, err = ioutil.ReadAll(os.Stdin)
|
||||
|
||||
default:
|
||||
data, err = ioutil.ReadFile(arg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = bytes.NewBuffer(data)
|
||||
return
|
||||
}
|
||||
|
||||
// FetchHTTPContent dials https for remote content
|
||||
func FetchHTTPContent(target string) ([]byte, error) {
|
||||
parsedTarget, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, newError("invalid URL: ", target).Base(err)
|
||||
}
|
||||
|
||||
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
||||
return nil, newError("invalid scheme: ", parsedTarget.Scheme)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: parsedTarget,
|
||||
Close: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, newError("failed to dial to ", target).Base(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := buf.ReadAllToBytes(resp.Body)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read HTTP response").Base(err)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
9
main/commands/all/errors.generated.go
Normal file
9
main/commands/all/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package all
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
18
main/commands/all/tls.go
Normal file
18
main/commands/all/tls.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package all
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/v1/main/commands/all/tlscmd"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdTLS = &base.Command{
|
||||
UsageLine: "{{.Exec}} tls",
|
||||
Short: "TLS tools",
|
||||
Long: `{{.Exec}} tls provides tools for TLS.
|
||||
`,
|
||||
|
||||
Commands: []*base.Command{
|
||||
tlscmd.CmdCert,
|
||||
tlscmd.CmdPing,
|
||||
},
|
||||
}
|
138
main/commands/all/tlscmd/cert.go
Normal file
138
main/commands/all/tlscmd/cert.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package tlscmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/protocol/tls/cert"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
// CmdCert is the tls cert command
|
||||
var CmdCert = &base.Command{
|
||||
UsageLine: "{{.Exec}} tls cert [--ca] [--domain=xray.com] [--expire=240h]",
|
||||
Short: "Generate TLS certificates",
|
||||
Long: `
|
||||
Generate TLS certificates.
|
||||
|
||||
The -domain=domain_name flag sets the domain name for the
|
||||
certificate.
|
||||
|
||||
The -org=organization flag sets the organization name for the
|
||||
certificate.
|
||||
|
||||
The -ca flag sets whether this certificate is a CA
|
||||
|
||||
The -json flag sets the output of certificate to JSON
|
||||
|
||||
The -file flag sets the certificate path to save.
|
||||
|
||||
The -expire flag expire time of the certificate. Default
|
||||
value 3 months.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdCert.Run = executeCert // break init loop
|
||||
}
|
||||
|
||||
var (
|
||||
certDomainNames stringList
|
||||
_ = func() bool {
|
||||
CmdCert.Flag.Var(&certDomainNames, "domain", "Domain name for the certificate")
|
||||
return true
|
||||
}()
|
||||
|
||||
certCommonName = CmdCert.Flag.String("name", "Xray Inc", "The common name of this certificate")
|
||||
certOrganization = CmdCert.Flag.String("org", "Xray Inc", "Organization of the certificate")
|
||||
certIsCA = CmdCert.Flag.Bool("ca", false, "Whether this certificate is a CA")
|
||||
certJSONOutput = CmdCert.Flag.Bool("json", true, "Print certificate in JSON format")
|
||||
certFileOutput = CmdCert.Flag.String("file", "", "Save certificate in file.")
|
||||
certExpire = CmdCert.Flag.Duration("expire", time.Hour*24*90 /* 90 days */, "Time until the certificate expires. Default value 3 months.")
|
||||
)
|
||||
|
||||
func executeCert(cmd *base.Command, args []string) {
|
||||
var opts []cert.Option
|
||||
if *certIsCA {
|
||||
opts = append(opts, cert.Authority(*certIsCA))
|
||||
opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
|
||||
}
|
||||
|
||||
opts = append(opts, cert.NotAfter(time.Now().Add(*certExpire)))
|
||||
opts = append(opts, cert.CommonName(*certCommonName))
|
||||
if len(certDomainNames) > 0 {
|
||||
opts = append(opts, cert.DNSNames(certDomainNames...))
|
||||
}
|
||||
opts = append(opts, cert.Organization(*certOrganization))
|
||||
|
||||
cert, err := cert.Generate(nil, opts...)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to generate TLS certificate: %s", err)
|
||||
}
|
||||
|
||||
if *certJSONOutput {
|
||||
printJSON(cert)
|
||||
}
|
||||
|
||||
if len(*certFileOutput) > 0 {
|
||||
if err := printFile(cert, *certFileOutput); err != nil {
|
||||
base.Fatalf("failed to save file: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func 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 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 printFile(certificate *cert.Certificate, name string) error {
|
||||
certPEM, keyPEM := certificate.ToPEM()
|
||||
return task.Run(context.Background(), func() error {
|
||||
return writeFile(certPEM, name+"_cert.pem")
|
||||
}, func() error {
|
||||
return writeFile(keyPEM, name+"_key.pem")
|
||||
})
|
||||
}
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (l *stringList) String() string {
|
||||
return "String list"
|
||||
}
|
||||
|
||||
func (l *stringList) Set(v string) error {
|
||||
if v == "" {
|
||||
base.Fatalf("empty value")
|
||||
}
|
||||
*l = append(*l, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
type jsonCert struct {
|
||||
Certificate []string `json:"certificate"`
|
||||
Key []string `json:"key"`
|
||||
}
|
111
main/commands/all/tlscmd/ping.go
Normal file
111
main/commands/all/tlscmd/ping.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package tlscmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
// CmdPing is the tls ping command
|
||||
var CmdPing = &base.Command{
|
||||
UsageLine: "{{.Exec}} tls ping [-ip <ip>] <domain>",
|
||||
Short: "Ping the domain with TLS handshake",
|
||||
Long: `
|
||||
Ping the domain with TLS handshake.
|
||||
|
||||
The -ip flag sets the IP address of the domain.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdPing.Run = executePing // break init loop
|
||||
}
|
||||
|
||||
var (
|
||||
pingIPStr = CmdPing.Flag.String("ip", "", "")
|
||||
)
|
||||
|
||||
func executePing(cmd *base.Command, args []string) {
|
||||
if CmdPing.Flag.NArg() < 1 {
|
||||
base.Fatalf("domain not specified")
|
||||
}
|
||||
|
||||
domain := CmdPing.Flag.Arg(0)
|
||||
fmt.Println("Tls ping: ", domain)
|
||||
|
||||
var ip net.IP
|
||||
if len(*pingIPStr) > 0 {
|
||||
v := net.ParseIP(*pingIPStr)
|
||||
if v == nil {
|
||||
base.Fatalf("invalid IP: %s", *pingIPStr)
|
||||
}
|
||||
ip = v
|
||||
} else {
|
||||
v, err := net.ResolveIPAddr("ip", domain)
|
||||
if err != nil {
|
||||
base.Fatalf("Failed to resolve IP: %s", err)
|
||||
}
|
||||
ip = v.IP
|
||||
}
|
||||
fmt.Println("Using IP: ", ip.String())
|
||||
|
||||
fmt.Println("-------------------")
|
||||
fmt.Println("Pinging without SNI")
|
||||
{
|
||||
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
||||
if err != nil {
|
||||
base.Fatalf("Failed to dial tcp: %s", err)
|
||||
}
|
||||
tlsConn := tls.Client(tcpConn, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
})
|
||||
err = tlsConn.Handshake()
|
||||
if err != nil {
|
||||
fmt.Println("Handshake failure: ", err)
|
||||
} else {
|
||||
fmt.Println("Handshake succeeded")
|
||||
printCertificates(tlsConn.ConnectionState().PeerCertificates)
|
||||
}
|
||||
tlsConn.Close()
|
||||
}
|
||||
|
||||
fmt.Println("-------------------")
|
||||
fmt.Println("Pinging with SNI")
|
||||
{
|
||||
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443})
|
||||
if err != nil {
|
||||
base.Fatalf("Failed to dial tcp: %s", err)
|
||||
}
|
||||
tlsConn := tls.Client(tcpConn, &tls.Config{
|
||||
ServerName: domain,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
})
|
||||
err = tlsConn.Handshake()
|
||||
if err != nil {
|
||||
fmt.Println("handshake failure: ", err)
|
||||
} else {
|
||||
fmt.Println("handshake succeeded")
|
||||
printCertificates(tlsConn.ConnectionState().PeerCertificates)
|
||||
}
|
||||
tlsConn.Close()
|
||||
}
|
||||
|
||||
fmt.Println("Tls ping finished")
|
||||
}
|
||||
|
||||
func printCertificates(certs []*x509.Certificate) {
|
||||
for _, cert := range certs {
|
||||
if len(cert.DNSNames) == 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Println("Allowed domains: ", cert.DNSNames)
|
||||
}
|
||||
}
|
22
main/commands/all/uuid.go
Normal file
22
main/commands/all/uuid.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package all
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/uuid"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdUUID = &base.Command{
|
||||
UsageLine: "{{.Exec}} uuid",
|
||||
Short: "Generate new UUIDs",
|
||||
Long: `
|
||||
Generate new UUIDs.
|
||||
`,
|
||||
Run: executeUUID,
|
||||
}
|
||||
|
||||
func executeUUID(cmd *base.Command, args []string) {
|
||||
u := uuid.New()
|
||||
fmt.Println(u.String())
|
||||
}
|
122
main/commands/base/command.go
Normal file
122
main/commands/base/command.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package base defines shared basic pieces of the commands,
|
||||
// in particular logging and the Command structure.
|
||||
package base
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Command is an implementation of a xray command
|
||||
// like xray run or xray version.
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string)
|
||||
|
||||
// UsageLine is the one-line usage message.
|
||||
// The words between "go" and the first flag or argument in the line are taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description shown in the 'go help' output.
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the 'go help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
|
||||
// CustomFlags indicates that the command will do its own
|
||||
// flag parsing.
|
||||
CustomFlags bool
|
||||
|
||||
// Commands lists the available commands and help topics.
|
||||
// The order here is the order in which they are printed by 'go help'.
|
||||
// Note that subcommands are in general best avoided.
|
||||
Commands []*Command
|
||||
}
|
||||
|
||||
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
|
||||
func (c *Command) LongName() string {
|
||||
name := c.UsageLine
|
||||
if i := strings.Index(name, " ["); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
if name == CommandEnv.Exec {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(name, CommandEnv.Exec+" ")
|
||||
}
|
||||
|
||||
// Name returns the command's short name: the last word in the usage line before a flag or argument.
|
||||
func (c *Command) Name() string {
|
||||
name := c.LongName()
|
||||
if i := strings.LastIndex(name, " "); i >= 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Usage prints usage of the Command
|
||||
func (c *Command) Usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
|
||||
fmt.Fprintf(os.Stderr, "Run 'xray help %s' for details.\n", c.LongName())
|
||||
SetExitStatus(2)
|
||||
Exit()
|
||||
}
|
||||
|
||||
// Runnable reports whether the command can be run; otherwise
|
||||
// it is a documentation pseudo-command such as importpath.
|
||||
func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
// Exit exits with code set with SetExitStatus()
|
||||
func Exit() {
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
// Fatalf logs error and exit with code 1
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
Errorf(format, args...)
|
||||
Exit()
|
||||
}
|
||||
|
||||
// Errorf logs error and set exit status to 1, but not exit
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
SetExitStatus(1)
|
||||
}
|
||||
|
||||
// ExitIfErrors exits if current status is not zero
|
||||
func ExitIfErrors() {
|
||||
if exitStatus != 0 {
|
||||
Exit()
|
||||
}
|
||||
}
|
||||
|
||||
var exitStatus = 0
|
||||
var exitMu sync.Mutex
|
||||
|
||||
// SetExitStatus set exit status code
|
||||
func SetExitStatus(n int) {
|
||||
exitMu.Lock()
|
||||
if exitStatus < n {
|
||||
exitStatus = n
|
||||
}
|
||||
exitMu.Unlock()
|
||||
}
|
||||
|
||||
// GetExitStatus get exit status code
|
||||
func GetExitStatus() int {
|
||||
return exitStatus
|
||||
}
|
23
main/commands/base/env.go
Normal file
23
main/commands/base/env.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// CommandEnvHolder is a struct holds the environment info of commands
|
||||
type CommandEnvHolder struct {
|
||||
Exec string
|
||||
}
|
||||
|
||||
// CommandEnv holds the environment info of commands
|
||||
var CommandEnv CommandEnvHolder
|
||||
|
||||
func init() {
|
||||
exec, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
CommandEnv.Exec = path.Base(exec)
|
||||
CommandEnv.Exec = "xray"
|
||||
}
|
88
main/commands/base/execute.go
Normal file
88
main/commands/base/execute.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// copied from "github.com/golang/go/main.go"
|
||||
|
||||
// Execute excute the commands
|
||||
func Execute() {
|
||||
buildCommandsText(RootCommand)
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
PrintUsage(os.Stderr, RootCommand)
|
||||
return
|
||||
}
|
||||
cmdName := args[0] // for error messages
|
||||
if args[0] == "help" {
|
||||
Help(os.Stdout, args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
BigCmdLoop:
|
||||
for bigCmd := RootCommand; ; {
|
||||
for _, cmd := range bigCmd.Commands {
|
||||
if cmd.Name() != args[0] {
|
||||
continue
|
||||
}
|
||||
if len(cmd.Commands) > 0 {
|
||||
// test sub commands
|
||||
bigCmd = cmd
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
PrintUsage(os.Stderr, bigCmd)
|
||||
SetExitStatus(2)
|
||||
Exit()
|
||||
}
|
||||
if args[0] == "help" {
|
||||
// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.
|
||||
Help(os.Stdout, append(strings.Split(cmdName, " "), args[1:]...))
|
||||
return
|
||||
}
|
||||
cmdName += " " + args[0]
|
||||
continue BigCmdLoop
|
||||
}
|
||||
if !cmd.Runnable() {
|
||||
continue
|
||||
}
|
||||
cmd.Flag.Usage = func() { cmd.Usage() }
|
||||
if cmd.CustomFlags {
|
||||
args = args[1:]
|
||||
} else {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
args = cmd.Flag.Args()
|
||||
}
|
||||
|
||||
cmd.Run(cmd, args)
|
||||
Exit()
|
||||
return
|
||||
}
|
||||
helpArg := ""
|
||||
if i := strings.LastIndex(cmdName, " "); i >= 0 {
|
||||
helpArg = " " + cmdName[:i]
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s %s: unknown command\nRun '%s help%s' for usage.\n", CommandEnv.Exec, cmdName, CommandEnv.Exec, helpArg)
|
||||
SetExitStatus(2)
|
||||
Exit()
|
||||
}
|
||||
}
|
||||
|
||||
// Sort sorts the commands
|
||||
func Sort() {
|
||||
sort.Slice(RootCommand.Commands, func(i, j int) bool {
|
||||
return SortLessFunc(RootCommand.Commands[i], RootCommand.Commands[j])
|
||||
})
|
||||
}
|
||||
|
||||
// SortLessFunc used for sort commands list, can be override from outside
|
||||
var SortLessFunc = func(i, j *Command) bool {
|
||||
return i.Name() < j.Name()
|
||||
}
|
157
main/commands/base/help.go
Normal file
157
main/commands/base/help.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Help implements the 'help' command.
|
||||
func Help(w io.Writer, args []string) {
|
||||
cmd := RootCommand
|
||||
Args:
|
||||
for i, arg := range args {
|
||||
for _, sub := range cmd.Commands {
|
||||
if sub.Name() == arg {
|
||||
cmd = sub
|
||||
continue Args
|
||||
}
|
||||
}
|
||||
|
||||
// helpSuccess is the help command using as many args as possible that would succeed.
|
||||
helpSuccess := CommandEnv.Exec + " help"
|
||||
if i > 0 {
|
||||
helpSuccess += " " + strings.Join(args[:i], " ")
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s help %s: unknown help topic. Run '%s'.\n", CommandEnv.Exec, strings.Join(args, " "), helpSuccess)
|
||||
SetExitStatus(2) // failed at 'xray help cmd'
|
||||
Exit()
|
||||
}
|
||||
|
||||
if len(cmd.Commands) > 0 {
|
||||
PrintUsage(os.Stdout, cmd)
|
||||
} else {
|
||||
tmpl(os.Stdout, helpTemplate, makeTmplData(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
var usageTemplate = `{{.Long | trim}}
|
||||
|
||||
Usage:
|
||||
|
||||
{{.Exec}} <command> [arguments]
|
||||
|
||||
The commands are:
|
||||
{{range .Commands}}{{if and (ne .Short "") (or (.Runnable) .Commands)}}
|
||||
{{.Name | printf "%-12s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
|
||||
`
|
||||
|
||||
// APPEND FOLLOWING TO 'usageTemplate' IF YOU WANT DOC,
|
||||
// A DOC TOPIC IS JUST A COMMAND NOT RUNNABLE:
|
||||
//
|
||||
// {{if eq (.UsageLine) (.Exec)}}
|
||||
// Additional help topics:
|
||||
// {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
|
||||
// {{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
|
||||
//
|
||||
// Use "{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
|
||||
// {{end}}
|
||||
|
||||
var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
`
|
||||
|
||||
// An errWriter wraps a writer, recording whether a write error occurred.
|
||||
type errWriter struct {
|
||||
w io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *errWriter) Write(b []byte) (int, error) {
|
||||
n, err := w.w.Write(b)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
|
||||
template.Must(t.Parse(text))
|
||||
ew := &errWriter{w: w}
|
||||
err := t.Execute(ew, data)
|
||||
if ew.err != nil {
|
||||
// I/O error writing. Ignore write on closed pipe.
|
||||
if strings.Contains(ew.err.Error(), "pipe") {
|
||||
SetExitStatus(1)
|
||||
Exit()
|
||||
}
|
||||
Fatalf("writing output: %v", ew.err)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func capitalize(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToTitle(r)) + s[n:]
|
||||
}
|
||||
|
||||
// PrintUsage prints usage of cmd to w
|
||||
func PrintUsage(w io.Writer, cmd *Command) {
|
||||
bw := bufio.NewWriter(w)
|
||||
tmpl(bw, usageTemplate, makeTmplData(cmd))
|
||||
bw.Flush()
|
||||
}
|
||||
|
||||
// buildCommandsText build text of command and its children as template
|
||||
func buildCommandsText(cmd *Command) {
|
||||
buildCommandText(cmd)
|
||||
for _, cmd := range cmd.Commands {
|
||||
buildCommandsText(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// buildCommandText build command text as template
|
||||
func buildCommandText(cmd *Command) {
|
||||
cmd.UsageLine = buildText(cmd.UsageLine, makeTmplData(cmd))
|
||||
cmd.Short = buildText(cmd.Short, makeTmplData(cmd))
|
||||
cmd.Long = buildText(cmd.Long, makeTmplData(cmd))
|
||||
}
|
||||
|
||||
func buildText(text string, data interface{}) string {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
tmpl(buf, text, data)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type tmplData struct {
|
||||
*Command
|
||||
*CommandEnvHolder
|
||||
}
|
||||
|
||||
func makeTmplData(cmd *Command) tmplData {
|
||||
return tmplData{
|
||||
Command: cmd,
|
||||
CommandEnvHolder: &CommandEnv,
|
||||
}
|
||||
}
|
16
main/commands/base/root.go
Normal file
16
main/commands/base/root.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package base
|
||||
|
||||
// RootCommand is the root command of all commands
|
||||
var RootCommand *Command
|
||||
|
||||
func init() {
|
||||
RootCommand = &Command{
|
||||
UsageLine: CommandEnv.Exec,
|
||||
Long: "The root command",
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCommand register a command to RootCommand
|
||||
func RegisterCommand(cmd *Command) {
|
||||
RootCommand.Commands = append(RootCommand.Commands, cmd)
|
||||
}
|
34
main/confloader/confloader.go
Normal file
34
main/confloader/confloader.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package confloader
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type configFileLoader func(string) (io.Reader, error)
|
||||
type extconfigLoader func([]string, io.Reader) (io.Reader, error)
|
||||
|
||||
var (
|
||||
EffectiveConfigFileLoader configFileLoader
|
||||
EffectiveExtConfigLoader extconfigLoader
|
||||
)
|
||||
|
||||
// LoadConfig reads from a path/url/stdin
|
||||
// actual work is in external module
|
||||
func LoadConfig(file string) (io.Reader, error) {
|
||||
if EffectiveConfigFileLoader == nil {
|
||||
newError("external config module not loaded, reading from stdin").AtInfo().WriteToLog()
|
||||
return os.Stdin, nil
|
||||
}
|
||||
return EffectiveConfigFileLoader(file)
|
||||
}
|
||||
|
||||
// LoadExtConfig calls xctl to handle multiple config
|
||||
// the actual work also in external module
|
||||
func LoadExtConfig(files []string, reader io.Reader) (io.Reader, error) {
|
||||
if EffectiveExtConfigLoader == nil {
|
||||
return nil, newError("external config module not loaded").AtError()
|
||||
}
|
||||
|
||||
return EffectiveExtConfigLoader(files, reader)
|
||||
}
|
9
main/confloader/errors.generated.go
Normal file
9
main/confloader/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package confloader
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
9
main/confloader/external/errors.generated.go
vendored
Normal file
9
main/confloader/external/errors.generated.go
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package external
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
87
main/confloader/external/external.go
vendored
Normal file
87
main/confloader/external/external.go
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
package external
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/platform/ctlcmd"
|
||||
"github.com/xtls/xray-core/v1/main/confloader"
|
||||
)
|
||||
|
||||
func ConfigLoader(arg string) (out io.Reader, err error) {
|
||||
var data []byte
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||
data, err = FetchHTTPContent(arg)
|
||||
|
||||
case arg == "stdin:":
|
||||
data, err = ioutil.ReadAll(os.Stdin)
|
||||
|
||||
default:
|
||||
data, err = ioutil.ReadFile(arg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = bytes.NewBuffer(data)
|
||||
return
|
||||
}
|
||||
|
||||
func FetchHTTPContent(target string) ([]byte, error) {
|
||||
parsedTarget, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, newError("invalid URL: ", target).Base(err)
|
||||
}
|
||||
|
||||
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
||||
return nil, newError("invalid scheme: ", parsedTarget.Scheme)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: parsedTarget,
|
||||
Close: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, newError("failed to dial to ", target).Base(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, newError("unexpected HTTP status code: ", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := buf.ReadAllToBytes(resp.Body)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read HTTP response").Base(err)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
|
||||
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.NewReader(buf.String()), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
confloader.EffectiveConfigFileLoader = ConfigLoader
|
||||
confloader.EffectiveExtConfigLoader = ExtConfigLoader
|
||||
}
|
71
main/distro/all/all.go
Normal file
71
main/distro/all/all.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package all
|
||||
|
||||
import (
|
||||
// The following are necessary as they register handlers in their init functions.
|
||||
|
||||
// Required features. Can't remove unless there is replacements.
|
||||
_ "github.com/xtls/xray-core/v1/app/dispatcher"
|
||||
_ "github.com/xtls/xray-core/v1/app/proxyman/inbound"
|
||||
_ "github.com/xtls/xray-core/v1/app/proxyman/outbound"
|
||||
|
||||
// Default commander and all its services. This is an optional feature.
|
||||
_ "github.com/xtls/xray-core/v1/app/commander"
|
||||
_ "github.com/xtls/xray-core/v1/app/log/command"
|
||||
_ "github.com/xtls/xray-core/v1/app/proxyman/command"
|
||||
_ "github.com/xtls/xray-core/v1/app/stats/command"
|
||||
|
||||
// Other optional features.
|
||||
_ "github.com/xtls/xray-core/v1/app/dns"
|
||||
_ "github.com/xtls/xray-core/v1/app/log"
|
||||
_ "github.com/xtls/xray-core/v1/app/policy"
|
||||
_ "github.com/xtls/xray-core/v1/app/reverse"
|
||||
_ "github.com/xtls/xray-core/v1/app/router"
|
||||
_ "github.com/xtls/xray-core/v1/app/stats"
|
||||
|
||||
// Inbound and outbound proxies.
|
||||
_ "github.com/xtls/xray-core/v1/proxy/blackhole"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/dns"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/dokodemo"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/freedom"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/http"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/mtproto"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/shadowsocks"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/socks"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/trojan"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/vless/inbound"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/vless/outbound"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/vmess/inbound"
|
||||
_ "github.com/xtls/xray-core/v1/proxy/vmess/outbound"
|
||||
|
||||
// Transports
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/domainsocket"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/http"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/quic"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/tcp"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/tls"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/udp"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/websocket"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/xtls"
|
||||
|
||||
// Transport headers
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/http"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/noop"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/srtp"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/tls"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/utp"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/wechat"
|
||||
_ "github.com/xtls/xray-core/v1/transport/internet/headers/wireguard"
|
||||
|
||||
// JSON config support. Choose only one from the two below.
|
||||
// The following line loads JSON from xctl
|
||||
// _ "github.com/xtls/xray-core/v1/main/json"
|
||||
// The following line loads JSON internally
|
||||
_ "github.com/xtls/xray-core/v1/main/jsonem"
|
||||
|
||||
// Load config from file or http(s)
|
||||
_ "github.com/xtls/xray-core/v1/main/confloader/external"
|
||||
|
||||
// commands
|
||||
_ "github.com/xtls/xray-core/v1/main/commands/all"
|
||||
)
|
11
main/distro/debug/debug.go
Normal file
11
main/distro/debug/debug.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
http.ListenAndServe(":6060", nil)
|
||||
}()
|
||||
}
|
9
main/errors.generated.go
Normal file
9
main/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
38
main/json/config_json.go
Normal file
38
main/json/config_json.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package json
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/cmdarg"
|
||||
core "github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/main/confloader"
|
||||
)
|
||||
|
||||
func init() {
|
||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
||||
Name: "JSON",
|
||||
Extension: []string{"json"},
|
||||
Loader: func(input interface{}) (*core.Config, error) {
|
||||
switch v := input.(type) {
|
||||
case cmdarg.Arg:
|
||||
r, err := confloader.LoadExtConfig(v, os.Stdin)
|
||||
if err != nil {
|
||||
return nil, newError("failed to execute xctl to convert config file.").Base(err).AtWarning()
|
||||
}
|
||||
return core.LoadConfig("protobuf", "", r)
|
||||
case io.Reader:
|
||||
r, err := confloader.LoadExtConfig([]string{"stdin:"}, os.Stdin)
|
||||
if err != nil {
|
||||
return nil, newError("failed to execute xctl to convert config file.").Base(err).AtWarning()
|
||||
}
|
||||
return core.LoadConfig("protobuf", "", r)
|
||||
default:
|
||||
return nil, newError("unknown type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
9
main/json/errors.generated.go
Normal file
9
main/json/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package json
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
9
main/jsonem/errors.generated.go
Normal file
9
main/jsonem/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package jsonem
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
38
main/jsonem/jsonem.go
Normal file
38
main/jsonem/jsonem.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package jsonem
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/cmdarg"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/infra/conf"
|
||||
"github.com/xtls/xray-core/v1/infra/conf/serial"
|
||||
"github.com/xtls/xray-core/v1/main/confloader"
|
||||
)
|
||||
|
||||
func init() {
|
||||
common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
|
||||
Name: "JSON",
|
||||
Extension: []string{"json"},
|
||||
Loader: func(input interface{}) (*core.Config, error) {
|
||||
switch v := input.(type) {
|
||||
case cmdarg.Arg:
|
||||
cf := &conf.Config{}
|
||||
for _, arg := range v {
|
||||
newError("Reading config: ", arg).AtInfo().WriteToLog()
|
||||
r, err := confloader.LoadConfig(arg)
|
||||
common.Must(err)
|
||||
c, err := serial.DecodeJSONConfig(r)
|
||||
common.Must(err)
|
||||
cf.Override(c, arg)
|
||||
}
|
||||
return cf.Build()
|
||||
case io.Reader:
|
||||
return serial.LoadJSONConfig(v)
|
||||
default:
|
||||
return nil, newError("unknow type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
61
main/main.go
Normal file
61
main/main.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
_ "github.com/xtls/xray-core/v1/main/distro/all"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Args = getArgsV4Compatible()
|
||||
|
||||
base.RootCommand.Long = "Xray is a platform for building proxies."
|
||||
base.RootCommand.Commands = append(
|
||||
[]*base.Command{
|
||||
cmdRun,
|
||||
cmdVersion,
|
||||
},
|
||||
base.RootCommand.Commands...,
|
||||
)
|
||||
base.Execute()
|
||||
}
|
||||
|
||||
func getArgsV4Compatible() []string {
|
||||
if len(os.Args) == 1 {
|
||||
return []string{os.Args[0], "run"}
|
||||
}
|
||||
if os.Args[1][0] != '-' {
|
||||
return os.Args
|
||||
}
|
||||
version := false
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fs.BoolVar(&version, "version", false, "")
|
||||
// parse silently, no usage, no error output
|
||||
fs.Usage = func() {}
|
||||
fs.SetOutput(&null{})
|
||||
err := fs.Parse(os.Args[1:])
|
||||
if err == flag.ErrHelp {
|
||||
//fmt.Println("DEPRECATED: -h, WILL BE REMOVED IN V5.")
|
||||
//fmt.Println("PLEASE USE: xray help")
|
||||
//fmt.Println()
|
||||
return []string{os.Args[0], "help"}
|
||||
}
|
||||
if version {
|
||||
//fmt.Println("DEPRECATED: -version, WILL BE REMOVED IN V5.")
|
||||
//fmt.Println("PLEASE USE: xray version")
|
||||
//fmt.Println()
|
||||
return []string{os.Args[0], "version"}
|
||||
}
|
||||
//fmt.Println("COMPATIBLE MODE, DEPRECATED.")
|
||||
//fmt.Println("PLEASE USE: xray run [arguments] INSTEAD.")
|
||||
//fmt.Println()
|
||||
return append([]string{os.Args[0], "run"}, os.Args[1:]...)
|
||||
}
|
||||
|
||||
type null struct{}
|
||||
|
||||
func (n *null) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
11
main/main_test.go
Normal file
11
main/main_test.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build coveragemain
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunMainForCoverage(t *testing.T) {
|
||||
main()
|
||||
}
|
169
main/run.go
Normal file
169
main/run.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/cmdarg"
|
||||
"github.com/xtls/xray-core/v1/common/platform"
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdRun = &base.Command{
|
||||
UsageLine: "{{.Exec}} run [-c config.json] [-confdir dir]",
|
||||
Short: "Run Xray with config, the default command",
|
||||
Long: `
|
||||
Run Xray with config, the default command.
|
||||
|
||||
The -config=file, -c=file flags set the config files for
|
||||
Xray. Multiple assign is accepted.
|
||||
|
||||
The -confdir=dir flag sets a dir with multiple json config
|
||||
|
||||
The -format=json flag sets the format of config files.
|
||||
Default "json".
|
||||
|
||||
The -test flag tells Xray to test config files only,
|
||||
without launching the server
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRun.Run = executeRun //break init loop
|
||||
}
|
||||
|
||||
var (
|
||||
configFiles cmdarg.Arg // "Config file for Xray.", the option is customed type, parse in main
|
||||
configDir string
|
||||
test = cmdRun.Flag.Bool("test", false, "Test config file only, without launching Xray server.")
|
||||
format = cmdRun.Flag.String("format", "json", "Format of input file.")
|
||||
|
||||
/* We have to do this here because Golang's Test will also need to parse flag, before
|
||||
* main func in this file is run.
|
||||
*/
|
||||
_ = func() bool {
|
||||
|
||||
cmdRun.Flag.Var(&configFiles, "config", "Config path for Xray.")
|
||||
cmdRun.Flag.Var(&configFiles, "c", "Short alias of -config")
|
||||
cmdRun.Flag.StringVar(&configDir, "confdir", "", "A dir with multiple json config")
|
||||
|
||||
return true
|
||||
}()
|
||||
)
|
||||
|
||||
func executeRun(cmd *base.Command, args []string) {
|
||||
printVersion()
|
||||
server, err := startXray()
|
||||
if err != nil {
|
||||
base.Fatalf("Filed to start: %s", err)
|
||||
}
|
||||
|
||||
if *test {
|
||||
fmt.Println("Configuration OK.")
|
||||
base.SetExitStatus(0)
|
||||
base.Exit()
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
base.Fatalf("Filed to start: %s", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
// Explicitly triggering GC to remove garbage from config loading.
|
||||
runtime.GC()
|
||||
|
||||
{
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)
|
||||
<-osSignals
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(file string) bool {
|
||||
info, err := os.Stat(file)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func dirExists(file string) bool {
|
||||
if file == "" {
|
||||
return false
|
||||
}
|
||||
info, err := os.Stat(file)
|
||||
return err == nil && info.IsDir()
|
||||
}
|
||||
|
||||
func readConfDir(dirPath string) {
|
||||
confs, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, f := range confs {
|
||||
if strings.HasSuffix(f.Name(), ".json") {
|
||||
configFiles.Set(path.Join(dirPath, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getConfigFilePath() cmdarg.Arg {
|
||||
if dirExists(configDir) {
|
||||
log.Println("Using confdir from arg:", configDir)
|
||||
readConfDir(configDir)
|
||||
} else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) {
|
||||
log.Println("Using confdir from env:", envConfDir)
|
||||
readConfDir(envConfDir)
|
||||
}
|
||||
|
||||
if len(configFiles) > 0 {
|
||||
return configFiles
|
||||
}
|
||||
|
||||
if workingDir, err := os.Getwd(); err == nil {
|
||||
configFile := filepath.Join(workingDir, "config.json")
|
||||
if fileExists(configFile) {
|
||||
log.Println("Using default config: ", configFile)
|
||||
return cmdarg.Arg{configFile}
|
||||
}
|
||||
}
|
||||
|
||||
if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
|
||||
log.Println("Using config from env: ", configFile)
|
||||
return cmdarg.Arg{configFile}
|
||||
}
|
||||
|
||||
log.Println("Using config from STDIN")
|
||||
return cmdarg.Arg{"stdin:"}
|
||||
}
|
||||
|
||||
func getConfigFormat() string {
|
||||
switch strings.ToLower(*format) {
|
||||
case "pb", "protobuf":
|
||||
return "protobuf"
|
||||
default:
|
||||
return "json"
|
||||
}
|
||||
}
|
||||
|
||||
func startXray() (core.Server, error) {
|
||||
configFiles := getConfigFilePath()
|
||||
|
||||
config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles)
|
||||
if err != nil {
|
||||
return nil, newError("failed to read config files: [", configFiles.String(), "]").Base(err)
|
||||
}
|
||||
|
||||
server, err := core.New(config)
|
||||
if err != nil {
|
||||
return nil, newError("failed to create server").Base(err)
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
27
main/version.go
Normal file
27
main/version.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xtls/xray-core/v1/core"
|
||||
"github.com/xtls/xray-core/v1/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdVersion = &base.Command{
|
||||
UsageLine: "{{.Exec}} version",
|
||||
Short: "Show current version of Xray",
|
||||
Long: `Version prints the build information for Xray executables.
|
||||
`,
|
||||
Run: executeVersion,
|
||||
}
|
||||
|
||||
func executeVersion(cmd *base.Command, args []string) {
|
||||
printVersion()
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
version := core.VersionStatement()
|
||||
for _, s := range version {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue