- 增加到期时间限制
 - 新增配置面板 https 访问后,http 自动跳转 https(同端口)
 - 降低获取系统连接数的 cpu 使用率
 - 优化界面
 - VMess 协议 alterId 默认改为 0
 - 修复旧版本 iOS 系统白屏问题
 - 修复重启面板后 xray 没有启动的问题
This commit is contained in:
sprov
2021-07-26 13:29:29 +08:00
parent f1057b1142
commit 292d5b89d4
22 changed files with 474 additions and 147 deletions

View File

@@ -1 +1 @@
0.2.0 0.3.0

View File

@@ -7,16 +7,22 @@ import (
var logger *logging.Logger var logger *logging.Logger
func init() {
InitLogger(logging.INFO)
}
func InitLogger(level logging.Level) { func InitLogger(level logging.Level) {
format := logging.MustStringFormatter( format := logging.MustStringFormatter(
`%{time:2006/01/02 15:04:05} %{level} - %{message}`, `%{time:2006/01/02 15:04:05} %{level} - %{message}`,
) )
logger = logging.MustGetLogger("x-ui") newLogger := logging.MustGetLogger("x-ui")
backend := logging.NewLogBackend(os.Stderr, "", 0) backend := logging.NewLogBackend(os.Stderr, "", 0)
backendFormatter := logging.NewBackendFormatter(backend, format) backendFormatter := logging.NewBackendFormatter(backend, format)
backendLeveled := logging.AddModuleLevel(backendFormatter) backendLeveled := logging.AddModuleLevel(backendFormatter)
backendLeveled.SetLevel(level, "") backendLeveled.SetLevel(level, "")
logger.SetBackend(backendLeveled) newLogger.SetBackend(backendLeveled)
logger = newLogger
} }
func Debug(args ...interface{}) { func Debug(args ...interface{}) {

0
util/sys/a.s Normal file
View File

8
util/sys/psutil.go Normal file
View File

@@ -0,0 +1,8 @@
package sys
import (
_ "unsafe"
)
//go:linkname HostProc github.com/shirou/gopsutil/internal/common.HostProc
func HostProc(combineWith ...string) string

23
util/sys/sys_darwin.go Normal file
View File

@@ -0,0 +1,23 @@
// +build darwin
package sys
import (
"github.com/shirou/gopsutil/net"
)
func GetTCPCount() (int, error) {
stats, err := net.Connections("tcp")
if err != nil {
return 0, err
}
return len(stats), nil
}
func GetUDPCount() (int, error) {
stats, err := net.Connections("udp")
if err != nil {
return 0, err
}
return len(stats), nil
}

70
util/sys/sys_linux.go Normal file
View File

@@ -0,0 +1,70 @@
// +build linux
package sys
import (
"bytes"
"fmt"
"io"
"os"
)
func getLinesNum(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, err
}
defer file.Close()
sum := 0
buf := make([]byte, 8192)
for {
n, err := file.Read(buf)
var buffPosition int
for {
i := bytes.IndexByte(buf[buffPosition:], '\n')
if i < 0 || n == buffPosition {
break
}
buffPosition += i + 1
sum++
}
if err == io.EOF {
return sum, nil
} else if err != nil {
return sum, err
}
}
}
func GetTCPCount() (int, error) {
root := HostProc()
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
if err != nil {
return tcp4, err
}
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
if err != nil {
return tcp4 + tcp6, nil
}
return tcp4 + tcp6, nil
}
func GetUDPCount() (int, error) {
root := HostProc()
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
if err != nil {
return udp4, err
}
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
if err != nil {
return udp4 + udp6, nil
}
return udp4 + udp6, nil
}

View File

@@ -1,14 +1,18 @@
class User { class User {
username = "";
password = ""; constructor() {
this.username = "";
this.password = "";
}
} }
class Msg { class Msg {
success = false;
msg = "";
obj = null;
constructor(success, msg, obj) { constructor(success, msg, obj) {
this.success = false;
this.msg = "";
this.obj = null;
if (success != null) { if (success != null) {
this.success = success; this.success = success;
} }
@@ -22,24 +26,25 @@ class Msg {
} }
class DBInbound { class DBInbound {
id = 0;
userId = 0;
up = 0;
down = 0;
total = 0;
remark = "";
enable = true;
expiryTime = 0;
listen = "";
port = 0;
protocol = "";
settings = "";
streamSettings = "";
tag = "";
sniffing = "";
constructor(data) { constructor(data) {
this.id = 0;
this.userId = 0;
this.up = 0;
this.down = 0;
this.total = 0;
this.remark = "";
this.enable = true;
this.expiryTime = 0;
this.listen = "";
this.port = 0;
this.protocol = "";
this.settings = "";
this.streamSettings = "";
this.tag = "";
this.sniffing = "";
if (data == null) { if (data == null) {
return; return;
} }
@@ -86,6 +91,25 @@ class DBInbound {
return address; return address;
} }
get _expiryTime() {
if (this.expiryTime === 0) {
return null;
}
return moment(this.expiryTime);
}
set _expiryTime(t) {
if (t == null) {
this.expiryTime = 0;
} else {
this.expiryTime = t.valueOf();
}
}
get isExpiry() {
return this.expiryTime < new Date().getTime();
}
toInbound() { toInbound() {
let settings = {}; let settings = {};
if (!ObjectUtil.isEmpty(this.settings)) { if (!ObjectUtil.isEmpty(this.settings)) {
@@ -132,17 +156,18 @@ class DBInbound {
} }
class AllSetting { class AllSetting {
webListen = "";
webPort = 54321;
webCertFile = "";
webKeyFile = "";
webBasePath = "/";
xrayTemplateConfig = "";
timeLocation = "Asia/Shanghai";
constructor(data) { constructor(data) {
this.webListen = "";
this.webPort = 54321;
this.webCertFile = "";
this.webKeyFile = "";
this.webBasePath = "/";
this.xrayTemplateConfig = "";
this.timeLocation = "Asia/Shanghai";
if (data == null) { if (data == null) {
return return
} }

View File

@@ -1165,7 +1165,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
} }
}; };
Inbound.VmessSettings.Vmess = class extends XrayCommonClass { Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
constructor(id=RandomUtil.randomUUID(), alterId=64) { constructor(id=RandomUtil.randomUUID(), alterId=0) {
super(); super();
this.id = id; this.id = id;
this.alterId = alterId; this.alterId = alterId;

View File

@@ -77,18 +77,19 @@ class PromiseUtil {
} }
const seq = [
'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
];
class RandomUtil { class RandomUtil {
static seq = [
'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
];
static randomIntRange(min, max) { static randomIntRange(min, max) {
return parseInt(Math.random() * (max - min) + min, 10); return parseInt(Math.random() * (max - min) + min, 10);
@@ -101,7 +102,7 @@ class RandomUtil {
static randomSeq(count) { static randomSeq(count) {
let str = ''; let str = '';
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
str += this.seq[this.randomInt(62)]; str += seq[this.randomInt(62)];
} }
return str; return str;
} }
@@ -109,7 +110,7 @@ class RandomUtil {
static randomLowerAndNum(count) { static randomLowerAndNum(count) {
let str = ''; let str = '';
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
str += this.seq[this.randomInt(36)]; str += seq[this.randomInt(36)];
} }
return str; return str;
} }
@@ -121,7 +122,7 @@ class RandomUtil {
if (index <= 9) { if (index <= 9) {
str += index; str += index;
} else { } else {
str += this.seq[index - 10]; str += seq[index - 10];
} }
} }
return str; return str;

View File

@@ -37,7 +37,7 @@ func (a *InboundController) startTask() {
c := webServer.GetCron() c := webServer.GetCron()
c.AddFunc("@every 10s", func() { c.AddFunc("@every 10s", func() {
if a.xrayService.IsNeedRestartAndSetFalse() { if a.xrayService.IsNeedRestartAndSetFalse() {
err := a.xrayService.RestartXray() err := a.xrayService.RestartXray(false)
if err != nil { if err != nil {
logger.Error("restart xray failed:", err) logger.Error("restart xray failed:", err)
} }

View File

@@ -39,6 +39,19 @@
</span> </span>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item>
<span slot="label">
到期时间
<a-tooltip>
<template slot="title">
留空则永不到期
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
</a-form-item>
</a-form> </a-form>
<!-- vmess settings --> <!-- vmess settings -->

View File

@@ -46,24 +46,44 @@
<div slot="title"> <div slot="title">
<a-button type="primary" icon="plus" @click="openAddInbound"></a-button> <a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
</div> </div>
<a-input v-model="searchKey" placeholder="search" autofocus style="max-width: 300px"></a-input> <!-- <a-input v-model="searchKey" placeholder="搜索" autofocus style="max-width: 300px"></a-input>-->
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id" <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
:data-source="dbInbounds" :data-source="dbInbounds"
:loading="spinning" :scroll="{ x: 1500 }" :loading="spinning" :scroll="{ x: 1500 }"
:pagination="false" :pagination="false"
style="margin-top: 20px" style="margin-top: 20px"
@change="() => getDBInbounds()"> @change="() => getDBInbounds()">
<template slot="action" slot-scope="text, dbInbound">
<a-dropdown :trigger="['click']">
<a @click="e => e.preventDefault()">操作</a>
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)">
<a-menu-item v-if="dbInbound.hasLink()" key="qrcode">
<a-icon type="qrcode"></a-icon>二维码
</a-menu-item>
<a-menu-item key="edit">
<a-icon type="edit"></a-icon>编辑
</a-menu-item>
<a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon>重置流量
</a-menu-item>
<a-menu-item key="delete">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon>删除
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template slot="protocol" slot-scope="text, dbInbound"> <template slot="protocol" slot-scope="text, dbInbound">
<a-tag color="blue">[[ dbInbound.protocol ]]</a-tag> <a-tag color="blue">[[ dbInbound.protocol ]]</a-tag>
</template> </template>
<template slot="traffic" slot-scope="text, dbInbound"> <template slot="traffic" slot-scope="text, dbInbound">
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]]</a-tag> <a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
<a-tag color="green">[[ sizeFormat(dbInbound.down) ]]</a-tag>
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
<a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag> <a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
<a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag> <a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
</template> </template>
<a-tag v-else color="cyan">无限制</a-tag> <a-tag v-else color="green">无限制</a-tag>
</template> </template>
<template slot="settings" slot-scope="text, dbInbound"> <template slot="settings" slot-scope="text, dbInbound">
<a-button type="link" @click="showInfo(dbInbound)">查看</a-button> <a-button type="link" @click="showInfo(dbInbound)">查看</a-button>
@@ -80,14 +100,15 @@
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch>
</template> </template>
<template slot="expiryTime" slot-scope="text, dbInbound"> <template slot="expiryTime" slot-scope="text, dbInbound">
<span v-if="dbInbound.expiryTime > 0" color="red">[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]</span> <template v-if="dbInbound.expiryTime > 0">
<span v-else>无限期</span> <a-tag v-if="dbInbound.isExpiry" color="red">
</template> [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
<template slot="action" slot-scope="text, dbInbound"> </a-tag>
<a-button v-if="dbInbound.hasLink()" type="primary" icon="qrcode" @click="showQrcode(dbInbound)"></a-button> <a-tag v-else color="blue">
<a-button type="primary" icon="edit" @click="openEditInbound(dbInbound)"></a-button> [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
<a-button icon="retweet" @click="resetTraffic(dbInbound)"></a-button> </a-tag>
<a-button type="danger" icon="delete" @click="delInbound(dbInbound)"></a-button> </template>
<a-tag v-else color="green">无限期</a-tag>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
@@ -100,14 +121,24 @@
<script> <script>
const columns = [{ const columns = [{
title: "操作",
align: 'center',
width: 30,
scopedSlots: { customRender: 'action' },
}, {
title: "启用",
align: 'center',
width: 40,
scopedSlots: { customRender: 'enable' },
}, {
title: "id", title: "id",
align: 'center', align: 'center',
dataIndex: "id", dataIndex: "id",
width: 60, width: 30,
}, { }, {
title: "备注", title: "备注",
align: 'center', align: 'center',
width: 60, width: 100,
dataIndex: "remark", dataIndex: "remark",
}, { }, {
title: "协议", title: "协议",
@@ -122,12 +153,12 @@
}, { }, {
title: "流量↑|↓", title: "流量↑|↓",
align: 'center', align: 'center',
width: 80, width: 150,
scopedSlots: { customRender: 'traffic' }, scopedSlots: { customRender: 'traffic' },
}, { }, {
title: "详细信息", title: "详细信息",
align: 'center', align: 'center',
width: 60, width: 40,
scopedSlots: { customRender: 'settings' }, scopedSlots: { customRender: 'settings' },
}, { }, {
title: "传输配置", title: "传输配置",
@@ -135,15 +166,10 @@
width: 60, width: 60,
scopedSlots: { customRender: 'stream' }, scopedSlots: { customRender: 'stream' },
}, { }, {
title: "启用", title: "到期时间",
align: 'center', align: 'center',
width: 60, width: 80,
scopedSlots: { customRender: 'enable' }, scopedSlots: { customRender: 'expiryTime' },
}, {
title: "操作",
align: 'center',
width: 60,
scopedSlots: { customRender: 'action' },
}]; }];
const app = new Vue({ const app = new Vue({
@@ -190,6 +216,22 @@
}); });
} }
}, },
clickAction(action, dbInbound) {
switch (action.key) {
case "qrcode":
this.showQrcode(dbInbound);
break;
case "edit":
this.openEditInbound(dbInbound);
break;
case "resetTraffic":
this.resetTraffic(dbInbound);
break;
case "delete":
this.delInbound(dbInbound);
break;
}
},
openAddInbound() { openAddInbound() {
inModal.show({ inModal.show({
title: '添加入站', title: '添加入站',
@@ -222,6 +264,7 @@
total: dbInbound.total, total: dbInbound.total,
remark: dbInbound.remark, remark: dbInbound.remark,
enable: dbInbound.enable, enable: dbInbound.enable,
expiryTime: dbInbound.expiryTime,
listen: inbound.listen, listen: inbound.listen,
port: inbound.port, port: inbound.port,
@@ -239,6 +282,7 @@
total: dbInbound.total, total: dbInbound.total,
remark: dbInbound.remark, remark: dbInbound.remark,
enable: dbInbound.enable, enable: dbInbound.enable,
expiryTime: dbInbound.expiryTime,
listen: inbound.listen, listen: inbound.listen,
port: inbound.port, port: inbound.port,

View File

@@ -188,8 +188,6 @@
Object.freeze(State); Object.freeze(State);
class CurTotal { class CurTotal {
current = 0
total = 0
constructor(current, total) { constructor(current, total) {
this.current = current; this.current = current;
@@ -216,19 +214,19 @@
} }
class Status { class Status {
cpu = new CurTotal(0, 0);
disk = new CurTotal(0, 0);
loads = [0, 0, 0];
mem = new CurTotal(0, 0);
netIO = {up: 0, down: 0};
netTraffic = {sent: 0, recv: 0};
swap = new CurTotal(0, 0);
tcpCount = 0;
udpCount = 0;
uptime = 0;
xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
constructor(data) { constructor(data) {
this.cpu = new CurTotal(0, 0);
this.disk = new CurTotal(0, 0);
this.loads = [0, 0, 0];
this.mem = new CurTotal(0, 0);
this.netIO = {up: 0, down: 0};
this.netTraffic = {sent: 0, recv: 0};
this.swap = new CurTotal(0, 0);
this.tcpCount = 0;
this.udpCount = 0;
this.uptime = 0;
this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
if (data == null) { if (data == null) {
return; return;
} }

View File

@@ -0,0 +1,25 @@
package job
import (
"x-ui/logger"
"x-ui/web/service"
)
type CheckInboundJob struct {
xrayService service.XrayService
inboundService service.InboundService
}
func NewCheckInboundJob() *CheckInboundJob {
return new(CheckInboundJob)
}
func (j *CheckInboundJob) Run() {
count, err := j.inboundService.DisableInvalidInbounds()
if err != nil {
logger.Warning("disable invalid inbounds err:", err)
} else if count > 0 {
logger.Debugf("disabled %v inbounds", count)
j.xrayService.SetToNeedRestart()
}
}

View File

@@ -0,0 +1,25 @@
package job
import "x-ui/web/service"
type CheckXrayRunningJob struct {
xrayService service.XrayService
checkTime int
}
func NewCheckXrayRunningJob() *CheckXrayRunningJob {
return new(CheckXrayRunningJob)
}
func (j *CheckXrayRunningJob) Run() {
if j.xrayService.IsXrayRunning() {
j.checkTime = 0
return
}
j.checkTime++
if j.checkTime < 2 {
return
}
j.xrayService.SetToNeedRestart()
}

View File

@@ -0,0 +1,30 @@
package job
import (
"x-ui/logger"
"x-ui/web/service"
)
type XrayTrafficJob struct {
xrayService service.XrayService
inboundService service.InboundService
}
func NewXrayTrafficJob() *XrayTrafficJob {
return new(XrayTrafficJob)
}
func (j *XrayTrafficJob) Run() {
if !j.xrayService.IsXrayRunning() {
return
}
traffics, err := j.xrayService.GetXrayTraffic()
if err != nil {
logger.Warning("get xray traffic failed:", err)
return
}
err = j.inboundService.AddTraffic(traffics)
if err != nil {
logger.Warning("add traffic failed:", err)
}
}

View File

@@ -0,0 +1,21 @@
package network
import "net"
type AutoHttpsListener struct {
net.Listener
}
func NewAutoHttpsListener(listener net.Listener) net.Listener {
return &AutoHttpsListener{
Listener: listener,
}
}
func (l *AutoHttpsListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return NewAutoHttpsConn(conn), nil
}

View File

@@ -0,0 +1,67 @@
package network
import (
"bufio"
"bytes"
"fmt"
"net"
"net/http"
"sync"
)
type AutoHttpsConn struct {
net.Conn
firstBuf []byte
bufStart int
readRequestOnce sync.Once
}
func NewAutoHttpsConn(conn net.Conn) net.Conn {
return &AutoHttpsConn{
Conn: conn,
}
}
func (c *AutoHttpsConn) readRequest() bool {
c.firstBuf = make([]byte, 2048)
n, err := c.Conn.Read(c.firstBuf)
c.firstBuf = c.firstBuf[:n]
if err != nil {
return false
}
reader := bytes.NewReader(c.firstBuf)
bufReader := bufio.NewReader(reader)
request, err := http.ReadRequest(bufReader)
if err != nil {
return false
}
resp := http.Response{
Header: http.Header{},
}
resp.StatusCode = http.StatusTemporaryRedirect
location := fmt.Sprintf("https://%v%v", request.Host, request.RequestURI)
resp.Header.Set("Location", location)
resp.Write(c.Conn)
c.Close()
c.firstBuf = nil
return true
}
func (c *AutoHttpsConn) Read(buf []byte) (int, error) {
c.readRequestOnce.Do(func() {
c.readRequest()
})
if c.firstBuf != nil {
n := copy(buf, c.firstBuf[c.bufStart:])
c.bufStart += n
if c.bufStart >= len(c.firstBuf) {
c.firstBuf = nil
}
return n, nil
}
return c.Conn.Read(buf)
}

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"fmt" "fmt"
"gorm.io/gorm" "gorm.io/gorm"
"time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/util/common" "x-ui/util/common"
@@ -166,8 +167,9 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) {
func (s *InboundService) DisableInvalidInbounds() (int64, error) { func (s *InboundService) DisableInvalidInbounds() (int64, error) {
db := database.GetDB() db := database.GetDB()
now := time.Now()
result := db.Model(model.Inbound{}). result := db.Model(model.Inbound{}).
Where("up + down >= total and total > 0 and enable = ?", true). Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
Update("enable", false) Update("enable", false)
err := result.Error err := result.Error
count := result.RowsAffected count := result.RowsAffected

View File

@@ -18,6 +18,7 @@ import (
"runtime" "runtime"
"time" "time"
"x-ui/logger" "x-ui/logger"
"x-ui/util/sys"
"x-ui/xray" "x-ui/xray"
) )
@@ -142,18 +143,14 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
logger.Warning("can not find io counters") logger.Warning("can not find io counters")
} }
tcpConnStats, err := net.Connections("tcp") status.TcpCount, err = sys.GetTCPCount()
if err != nil { if err != nil {
logger.Warning("get connections failed:", err) logger.Warning("get tcp connections failed:", err)
} else {
status.TcpCount = len(tcpConnStats)
} }
udpConnStats, err := net.Connections("udp") status.UdpCount, err = sys.GetUDPCount()
if err != nil { if err != nil {
logger.Warning("get connections failed:", err) logger.Warning("get udp connections failed:", err)
} else {
status.UdpCount = len(udpConnStats)
} }
if s.xrayService.IsXrayRunning() { if s.xrayService.IsXrayRunning() {
@@ -265,7 +262,7 @@ func (s *ServerService) UpdateXray(version string) error {
s.xrayService.StopXray() s.xrayService.StopXray()
defer func() { defer func() {
err := s.xrayService.RestartXray() err := s.xrayService.RestartXray(true)
if err != nil { if err != nil {
logger.Error("start xray failed:", err) logger.Error("start xray failed:", err)
} }

View File

@@ -84,7 +84,7 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, error) {
return p.GetTraffic(true) return p.GetTraffic(true)
} }
func (s *XrayService) RestartXray() error { func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
logger.Debug("restart xray") logger.Debug("restart xray")
@@ -94,8 +94,9 @@ func (s *XrayService) RestartXray() error {
return err return err
} }
if p != nil { if p != nil && p.IsRunning() {
if p.GetConfig().Equals(xrayConfig) { if !isForce && p.GetConfig().Equals(xrayConfig) {
logger.Debug("not need to restart xray")
return nil return nil
} }
p.Stop() p.Stop()

View File

@@ -24,6 +24,8 @@ import (
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
"x-ui/web/controller" "x-ui/web/controller"
"x-ui/web/job"
"x-ui/web/network"
"x-ui/web/service" "x-ui/web/service"
) )
@@ -277,53 +279,21 @@ func (s *Server) initI18n(engine *gin.Engine) error {
} }
func (s *Server) startTask() { func (s *Server) startTask() {
err := s.xrayService.RestartXray() err := s.xrayService.RestartXray(true)
if err != nil { if err != nil {
logger.Warning("start xray failed:", err) logger.Warning("start xray failed:", err)
} }
var checkTime = 0
// 每 30 秒检查一次 xray 是否在运行 // 每 30 秒检查一次 xray 是否在运行
s.cron.AddFunc("@every 30s", func() { s.cron.AddJob("@every 30s", job.NewCheckXrayRunningJob())
if s.xrayService.IsXrayRunning() {
checkTime = 0
return
}
checkTime++
if checkTime < 2 {
return
}
s.xrayService.SetToNeedRestart()
})
go func() { go func() {
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
// 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开 // 每 10 秒统计一次流量,首次启动延迟 5 秒,与重启 xray 的时间错开
s.cron.AddFunc("@every 10s", func() { s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
if !s.xrayService.IsXrayRunning() {
return
}
traffics, err := s.xrayService.GetXrayTraffic()
if err != nil {
logger.Warning("get xray traffic failed:", err)
return
}
err = s.inboundService.AddTraffic(traffics)
if err != nil {
logger.Warning("add traffic failed:", err)
}
})
}() }()
// 每 30 秒检查一次 inbound 流量超出情况 // 每 30 秒检查一次 inbound 流量超出和到期的情况
s.cron.AddFunc("@every 30s", func() { s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
count, err := s.inboundService.DisableInvalidInbounds()
if err != nil {
logger.Warning("disable invalid inbounds err:", err)
} else if count > 0 {
logger.Debugf("disabled %v inbounds", count)
s.xrayService.SetToNeedRestart()
}
})
} }
func (s *Server) Start() (err error) { func (s *Server) Start() (err error) {
@@ -362,22 +332,21 @@ func (s *Server) Start() (err error) {
return err return err
} }
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
var listener net.Listener listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return err
}
if certFile != "" || keyFile != "" { if certFile != "" || keyFile != "" {
var cert tls.Certificate cert, err := tls.LoadX509KeyPair(certFile, keyFile)
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil { if err != nil {
listener.Close()
return err return err
} }
c := &tls.Config{ c := &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
} }
listener, err = tls.Listen("tcp", listenAddr, c) listener = network.NewAutoHttpsListener(listener)
} else { listener = tls.NewListener(listener, c)
listener, err = net.Listen("tcp", listenAddr)
}
if err != nil {
return err
} }
if certFile != "" || keyFile != "" { if certFile != "" || keyFile != "" {
logger.Info("web server run https on", listener.Addr()) logger.Info("web server run https on", listener.Addr())
@@ -392,7 +361,9 @@ func (s *Server) Start() (err error) {
Handler: engine, Handler: engine,
} }
go s.httpServer.Serve(listener) go func() {
s.httpServer.Serve(listener)
}()
return nil return nil
} }