Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/xray
diff options
context:
space:
mode:
Diffstat (limited to 'xray')
-rw-r--r--xray/client_traffic.go12
-rw-r--r--xray/config.go62
-rw-r--r--xray/inbound.go41
-rw-r--r--xray/process.go313
-rw-r--r--xray/traffic.go8
5 files changed, 436 insertions, 0 deletions
diff --git a/xray/client_traffic.go b/xray/client_traffic.go
new file mode 100644
index 00000000..4df6a502
--- /dev/null
+++ b/xray/client_traffic.go
@@ -0,0 +1,12 @@
+package xray
+
+type ClientTraffic struct {
+ Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
+ InboundId int `json:"inboundId" form:"inboundId"`
+ Enable bool `json:"enable" form:"enable"`
+ Email string `json:"email" form:"email" gorm:"unique"`
+ Up int64 `json:"up" form:"up"`
+ Down int64 `json:"down" form:"down"`
+ ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
+ Total int64 `json:"total" form:"total"`
+}
diff --git a/xray/config.go b/xray/config.go
new file mode 100644
index 00000000..cc63ca40
--- /dev/null
+++ b/xray/config.go
@@ -0,0 +1,62 @@
+package xray
+
+import (
+ "bytes"
+ "x-ui/util/json_util"
+)
+
+type Config struct {
+ LogConfig json_util.RawMessage `json:"log"`
+ RouterConfig json_util.RawMessage `json:"routing"`
+ DNSConfig json_util.RawMessage `json:"dns"`
+ InboundConfigs []InboundConfig `json:"inbounds"`
+ OutboundConfigs json_util.RawMessage `json:"outbounds"`
+ Transport json_util.RawMessage `json:"transport"`
+ Policy json_util.RawMessage `json:"policy"`
+ API json_util.RawMessage `json:"api"`
+ Stats json_util.RawMessage `json:"stats"`
+ Reverse json_util.RawMessage `json:"reverse"`
+ FakeDNS json_util.RawMessage `json:"fakeDns"`
+}
+
+func (c *Config) Equals(other *Config) bool {
+ if len(c.InboundConfigs) != len(other.InboundConfigs) {
+ return false
+ }
+ for i, inbound := range c.InboundConfigs {
+ if !inbound.Equals(&other.InboundConfigs[i]) {
+ return false
+ }
+ }
+ if !bytes.Equal(c.LogConfig, other.LogConfig) {
+ return false
+ }
+ if !bytes.Equal(c.RouterConfig, other.RouterConfig) {
+ return false
+ }
+ if !bytes.Equal(c.DNSConfig, other.DNSConfig) {
+ return false
+ }
+ if !bytes.Equal(c.OutboundConfigs, other.OutboundConfigs) {
+ return false
+ }
+ if !bytes.Equal(c.Transport, other.Transport) {
+ return false
+ }
+ if !bytes.Equal(c.Policy, other.Policy) {
+ return false
+ }
+ if !bytes.Equal(c.API, other.API) {
+ return false
+ }
+ if !bytes.Equal(c.Stats, other.Stats) {
+ return false
+ }
+ if !bytes.Equal(c.Reverse, other.Reverse) {
+ return false
+ }
+ if !bytes.Equal(c.FakeDNS, other.FakeDNS) {
+ return false
+ }
+ return true
+}
diff --git a/xray/inbound.go b/xray/inbound.go
new file mode 100644
index 00000000..461c2ee7
--- /dev/null
+++ b/xray/inbound.go
@@ -0,0 +1,41 @@
+package xray
+
+import (
+ "bytes"
+ "x-ui/util/json_util"
+)
+
+type InboundConfig struct {
+ Listen json_util.RawMessage `json:"listen"` // listen 不能为空字符串
+ Port int `json:"port"`
+ Protocol string `json:"protocol"`
+ Settings json_util.RawMessage `json:"settings"`
+ StreamSettings json_util.RawMessage `json:"streamSettings"`
+ Tag string `json:"tag"`
+ Sniffing json_util.RawMessage `json:"sniffing"`
+}
+
+func (c *InboundConfig) Equals(other *InboundConfig) bool {
+ if !bytes.Equal(c.Listen, other.Listen) {
+ return false
+ }
+ if c.Port != other.Port {
+ return false
+ }
+ if c.Protocol != other.Protocol {
+ return false
+ }
+ if !bytes.Equal(c.Settings, other.Settings) {
+ return false
+ }
+ if !bytes.Equal(c.StreamSettings, other.StreamSettings) {
+ return false
+ }
+ if c.Tag != other.Tag {
+ return false
+ }
+ if !bytes.Equal(c.Sniffing, other.Sniffing) {
+ return false
+ }
+ return true
+}
diff --git a/xray/process.go b/xray/process.go
new file mode 100644
index 00000000..ffa933ca
--- /dev/null
+++ b/xray/process.go
@@ -0,0 +1,313 @@
+package xray
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "regexp"
+ "runtime"
+ "strings"
+ "time"
+ "x-ui/util/common"
+
+ "github.com/Workiva/go-datastructures/queue"
+ statsservice "github.com/xtls/xray-core/app/stats/command"
+ "google.golang.org/grpc"
+)
+
+var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
+var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
+
+func GetBinaryName() string {
+ return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func GetBinaryPath() string {
+ return "bin/" + GetBinaryName()
+}
+
+func GetConfigPath() string {
+ return "bin/config.json"
+}
+
+func GetGeositePath() string {
+ return "bin/geosite.dat"
+}
+
+func GetGeoipPath() string {
+ return "bin/geoip.dat"
+}
+
+func stopProcess(p *Process) {
+ p.Stop()
+}
+
+type Process struct {
+ *process
+}
+
+func NewProcess(xrayConfig *Config) *Process {
+ p := &Process{newProcess(xrayConfig)}
+ runtime.SetFinalizer(p, stopProcess)
+ return p
+}
+
+type process struct {
+ cmd *exec.Cmd
+
+ version string
+ apiPort int
+
+ config *Config
+ lines *queue.Queue
+ exitErr error
+}
+
+func newProcess(config *Config) *process {
+ return &process{
+ version: "Unknown",
+ config: config,
+ lines: queue.New(100),
+ }
+}
+
+func (p *process) IsRunning() bool {
+ if p.cmd == nil || p.cmd.Process == nil {
+ return false
+ }
+ if p.cmd.ProcessState == nil {
+ return true
+ }
+ return false
+}
+
+func (p *process) GetErr() error {
+ return p.exitErr
+}
+
+func (p *process) GetResult() string {
+ if p.lines.Empty() && p.exitErr != nil {
+ return p.exitErr.Error()
+ }
+ items, _ := p.lines.TakeUntil(func(item interface{}) bool {
+ return true
+ })
+ lines := make([]string, 0, len(items))
+ for _, item := range items {
+ lines = append(lines, item.(string))
+ }
+ return strings.Join(lines, "\n")
+}
+
+func (p *process) GetVersion() string {
+ return p.version
+}
+
+func (p *Process) GetAPIPort() int {
+ return p.apiPort
+}
+
+func (p *Process) GetConfig() *Config {
+ return p.config
+}
+
+func (p *process) refreshAPIPort() {
+ for _, inbound := range p.config.InboundConfigs {
+ if inbound.Tag == "api" {
+ p.apiPort = inbound.Port
+ break
+ }
+ }
+}
+
+func (p *process) refreshVersion() {
+ cmd := exec.Command(GetBinaryPath(), "-version")
+ data, err := cmd.Output()
+ if err != nil {
+ p.version = "Unknown"
+ } else {
+ datas := bytes.Split(data, []byte(" "))
+ if len(datas) <= 1 {
+ p.version = "Unknown"
+ } else {
+ p.version = string(datas[1])
+ }
+ }
+}
+
+func (p *process) Start() (err error) {
+ if p.IsRunning() {
+ return errors.New("xray is already running")
+ }
+
+ defer func() {
+ if err != nil {
+ p.exitErr = err
+ }
+ }()
+
+ data, err := json.MarshalIndent(p.config, "", " ")
+ if err != nil {
+ return common.NewErrorf("生成 xray 配置文件失败: %v", err)
+ }
+ configPath := GetConfigPath()
+ err = os.WriteFile(configPath, data, fs.ModePerm)
+ if err != nil {
+ return common.NewErrorf("写入配置文件失败: %v", err)
+ }
+
+ cmd := exec.Command(GetBinaryPath(), "-c", configPath)
+ p.cmd = cmd
+
+ stdReader, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ errReader, err := cmd.StderrPipe()
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ defer func() {
+ common.Recover("")
+ stdReader.Close()
+ }()
+ reader := bufio.NewReaderSize(stdReader, 8192)
+ for {
+ line, _, err := reader.ReadLine()
+ if err != nil {
+ return
+ }
+ if p.lines.Len() >= 100 {
+ p.lines.Get(1)
+ }
+ p.lines.Put(string(line))
+ }
+ }()
+
+ go func() {
+ defer func() {
+ common.Recover("")
+ errReader.Close()
+ }()
+ reader := bufio.NewReaderSize(errReader, 8192)
+ for {
+ line, _, err := reader.ReadLine()
+ if err != nil {
+ return
+ }
+ if p.lines.Len() >= 100 {
+ p.lines.Get(1)
+ }
+ p.lines.Put(string(line))
+ }
+ }()
+
+ go func() {
+ err := cmd.Run()
+ if err != nil {
+ p.exitErr = err
+ }
+ }()
+
+ p.refreshVersion()
+ p.refreshAPIPort()
+
+ return nil
+}
+
+func (p *process) Stop() error {
+ if !p.IsRunning() {
+ return errors.New("xray is not running")
+ }
+ return p.cmd.Process.Kill()
+}
+
+func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
+ if p.apiPort == 0 {
+ return nil, nil, common.NewError("xray api port wrong:", p.apiPort)
+ }
+ conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", p.apiPort), grpc.WithInsecure())
+ if err != nil {
+ return nil, nil, err
+ }
+ defer conn.Close()
+
+ client := statsservice.NewStatsServiceClient(conn)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+ defer cancel()
+ request := &statsservice.QueryStatsRequest{
+ Reset_: reset,
+ }
+ resp, err := client.QueryStats(ctx, request)
+ if err != nil {
+ return nil, nil, err
+ }
+ tagTrafficMap := map[string]*Traffic{}
+ emailTrafficMap := map[string]*ClientTraffic{}
+
+ clientTraffics := make([]*ClientTraffic, 0)
+ traffics := make([]*Traffic, 0)
+ for _, stat := range resp.GetStat() {
+ matchs := trafficRegex.FindStringSubmatch(stat.Name)
+ if len(matchs) < 3 {
+
+ matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
+ if len(matchs) < 3 {
+ continue
+ } else {
+
+ isUser := matchs[1] == "user"
+ email := matchs[2]
+ isDown := matchs[3] == "downlink"
+ if !isUser {
+ continue
+ }
+ traffic, ok := emailTrafficMap[email]
+ if !ok {
+ traffic = &ClientTraffic{
+ Email: email,
+ }
+ emailTrafficMap[email] = traffic
+ clientTraffics = append(clientTraffics, traffic)
+ }
+ if isDown {
+ traffic.Down = stat.Value
+ } else {
+ traffic.Up = stat.Value
+ }
+
+ }
+ continue
+ }
+ isInbound := matchs[1] == "inbound"
+ tag := matchs[2]
+ isDown := matchs[3] == "downlink"
+ if tag == "api" {
+ continue
+ }
+ traffic, ok := tagTrafficMap[tag]
+ if !ok {
+ traffic = &Traffic{
+ IsInbound: isInbound,
+ Tag: tag,
+ }
+ tagTrafficMap[tag] = traffic
+ traffics = append(traffics, traffic)
+ }
+ if isDown {
+ traffic.Down = stat.Value
+ } else {
+ traffic.Up = stat.Value
+ }
+ }
+
+ return traffics, clientTraffics, nil
+}
diff --git a/xray/traffic.go b/xray/traffic.go
new file mode 100644
index 00000000..a1ef5186
--- /dev/null
+++ b/xray/traffic.go
@@ -0,0 +1,8 @@
+package xray
+
+type Traffic struct {
+ IsInbound bool
+ Tag string
+ Up int64
+ Down int64
+}