diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 5313d60a..ce3a94f0 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -7,7 +7,7 @@ on:
jobs:
build:
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ba3aac82..222f198a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,7 +31,7 @@ jobs:
- 386
- armv5
- s390x
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 09073593..6d2a77a4 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -560,6 +560,8 @@ class TlsStreamSettings extends XrayCommonClass {
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
+ echServerKeys = '',
+ echForceQuery = 'none',
settings = new TlsStreamSettings.Settings()
) {
super();
@@ -573,6 +575,8 @@ class TlsStreamSettings extends XrayCommonClass {
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
this.alpn = alpn;
+ this.echServerKeys = echServerKeys;
+ this.echForceQuery = echForceQuery;
this.settings = settings;
}
@@ -592,7 +596,7 @@ class TlsStreamSettings extends XrayCommonClass {
}
if (!ObjectUtil.isEmpty(json.settings)) {
- settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains);
+ settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.echConfigList);
}
return new TlsStreamSettings(
json.serverName,
@@ -605,6 +609,8 @@ class TlsStreamSettings extends XrayCommonClass {
json.enableSessionResumption,
certs,
json.alpn,
+ json.echServerKeys,
+ json.echForceQuery,
settings,
);
}
@@ -621,6 +627,8 @@ class TlsStreamSettings extends XrayCommonClass {
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn,
+ echServerKeys: this.echServerKeys,
+ echForceQuery: this.echForceQuery,
settings: this.settings,
};
}
@@ -701,21 +709,25 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(
allowInsecure = false,
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
+ echConfigList = '',
) {
super();
this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint;
+ this.echConfigList = echConfigList;
}
static fromJson(json = {}) {
return new TlsStreamSettings.Settings(
json.allowInsecure,
json.fingerprint,
+ json.echConfigList,
);
}
toJson() {
return {
allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint,
+ echConfigList: this.echConfigList
};
}
};
@@ -1375,6 +1387,9 @@ class Inbound extends XrayCommonClass {
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
+ if (this.stream.tls.settings.echConfigList?.length > 0) {
+ params.set("ech", this.stream.tls.settings.echConfigList);
+ }
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
}
@@ -1474,6 +1489,9 @@ class Inbound extends XrayCommonClass {
if (this.stream.tls.settings.allowInsecure) {
params.set("allowInsecure", "1");
}
+ if (this.stream.tls.settings.echConfigList?.length > 0) {
+ params.set("ech", this.stream.tls.settings.echConfigList);
+ }
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
@@ -1552,6 +1570,9 @@ class Inbound extends XrayCommonClass {
if (this.stream.tls.settings.allowInsecure) {
params.set("allowInsecure", "1");
}
+ if (this.stream.tls.settings.echConfigList?.length > 0) {
+ params.set("ech", this.stream.tls.settings.echConfigList);
+ }
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
params.set("sni", this.stream.tls.sni);
}
@@ -2291,12 +2312,14 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
protocol,
address,
port,
+ portMap = [],
network = 'tcp,udp',
followRedirect = false
) {
super(protocol);
this.address = address;
this.port = port;
+ this.portMap = portMap;
this.network = network;
this.followRedirect = followRedirect;
}
@@ -2306,6 +2329,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
Protocols.DOKODEMO,
json.address,
json.port,
+ XrayCommonClass.toHeaders(json.portMap),
json.network,
json.followRedirect,
);
@@ -2315,6 +2339,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
return {
address: this.address,
port: this.port,
+ portMap: XrayCommonClass.toV2Headers(this.portMap, false),
network: this.network,
followRedirect: this.followRedirect,
};
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 03569b00..a42c400d 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -354,13 +354,15 @@ class TlsStreamSettings extends CommonClass {
serverName = '',
alpn = [],
fingerprint = '',
- allowInsecure = false
+ allowInsecure = false,
+ echConfigList = '',
) {
super();
this.serverName = serverName;
this.alpn = alpn;
this.fingerprint = fingerprint;
this.allowInsecure = allowInsecure;
+ this.echConfigList = echConfigList;
}
static fromJson(json = {}) {
@@ -369,6 +371,7 @@ class TlsStreamSettings extends CommonClass {
json.alpn,
json.fingerprint,
json.allowInsecure,
+ json.echConfigList,
);
}
@@ -378,6 +381,7 @@ class TlsStreamSettings extends CommonClass {
alpn: this.alpn,
fingerprint: this.fingerprint,
allowInsecure: this.allowInsecure,
+ echConfigList: this.echConfigList
};
}
}
@@ -782,7 +786,8 @@ class Outbound extends CommonClass {
let alpn = url.searchParams.get('alpn');
let allowInsecure = url.searchParams.get('allowInsecure');
let sni = url.searchParams.get('sni') ?? '';
- stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
+ let ech = url.searchParams.get('ech') ?? '';
+ stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
}
if (security == 'reality') {
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index a89f224f..851b4b6f 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -108,8 +108,8 @@ func (a *InboundController) addInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
- jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
- if err == nil && needRestart {
+ jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, nil)
+ if needRestart {
a.xrayService.SetToNeedRestart()
}
}
@@ -126,8 +126,8 @@ func (a *InboundController) delInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
- jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, err)
- if err == nil && needRestart {
+ jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, nil)
+ if needRestart {
a.xrayService.SetToNeedRestart()
}
}
@@ -152,8 +152,8 @@ func (a *InboundController) updateInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
- jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, err)
- if err == nil && needRestart {
+ jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, nil)
+ if needRestart {
a.xrayService.SetToNeedRestart()
}
}
@@ -342,25 +342,25 @@ func (a *InboundController) onlines(c *gin.Context) {
func (a *InboundController) updateClientTraffic(c *gin.Context) {
email := c.Param("email")
-
+
// Define the request structure for traffic update
type TrafficUpdateRequest struct {
Upload int64 `json:"upload"`
Download int64 `json:"download"`
}
-
+
var request TrafficUpdateRequest
err := c.ShouldBindJSON(&request)
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
return
}
-
+
err = a.inboundService.UpdateClientTrafficByEmail(email, request.Upload, request.Download)
if err != nil {
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
return
}
-
+
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
}
diff --git a/web/controller/server.go b/web/controller/server.go
index 9e167153..dd001f5e 100644
--- a/web/controller/server.go
+++ b/web/controller/server.go
@@ -52,6 +52,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
g.POST("/getNewmldsa65", a.getNewmldsa65)
+ g.POST("/getNewEchCert", a.getNewEchCert)
}
func (a *ServerController) refreshStatus() {
@@ -215,3 +216,13 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) {
}
jsonObj(c, cert, nil)
}
+
+func (a *ServerController) getNewEchCert(c *gin.Context) {
+ sni := c.PostForm("sni")
+ cert, err := a.serverService.GetNewEchCert(sni)
+ if err != nil {
+ jsonMsg(c, "get ech certificate", err)
+ return
+ }
+ jsonObj(c, cert, nil)
+}
diff --git a/web/html/form/protocol/dokodemo.html b/web/html/form/protocol/dokodemo.html
index 70ffe7e0..267dcf4e 100644
--- a/web/html/form/protocol/dokodemo.html
+++ b/web/html/form/protocol/dokodemo.html
@@ -6,6 +6,19 @@
+
+ +
+
+
+
+
+ [[ index+1 ]]
+
+
+ -
+
+
+
TCP,UDP
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 0de6dae5..3e61b5a2 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -106,6 +106,21 @@
+
+
+
+
+
+
+
+
+ [[ key ]]
+
+
+
+ Get New ECH Cert
+
diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html
index f11df2e2..b77e74e2 100644
--- a/web/html/modals/inbound_modal.html
+++ b/web/html/modals/inbound_modal.html
@@ -152,6 +152,16 @@
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
+ async getNewEchCert() {
+ inModal.loading(true);
+ const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
+ inModal.loading(false);
+ if (!msg.success) {
+ return;
+ }
+ inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
+ inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
+ },
},
});
diff --git a/web/service/server.go b/web/service/server.go
index ea118f13..ade5b521 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -775,3 +775,27 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
return keyPair, nil
}
+
+func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
+ // Run the command
+ cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ return nil, err
+ }
+
+ lines := strings.Split(out.String(), "\n")
+ if len(lines) < 4 {
+ return nil, common.NewError("invalid ech cert")
+ }
+
+ configList := lines[1]
+ serverKeys := lines[3]
+
+ return map[string]interface{}{
+ "echServerKeys": serverKeys,
+ "echConfigList": configList,
+ }, nil
+}
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index c3748499..3c2fc04f 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -158,6 +158,7 @@
"remark" = "ملاحظة"
"protocol" = "بروتوكول"
"port" = "بورت"
+"portMap" = "خريطة البورت"
"traffic" = "الترافيك"
"details" = "تفاصيل"
"transportConfig" = "نقل"
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index eb2389dd..4ddef0da 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -158,6 +158,7 @@
"remark" = "Remark"
"protocol" = "Protocol"
"port" = "Port"
+"portMap" = "Port Mapping"
"traffic" = "Traffic"
"details" = "Details"
"transportConfig" = "Transport"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index e682bd38..51a5aea8 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -158,6 +158,7 @@
"remark" = "Notas"
"protocol" = "Protocolo"
"port" = "Puerto"
+"portMap" = "Puertos de Destino"
"traffic" = "Tráfico"
"details" = "Detalles"
"transportConfig" = "Transporte"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 4d3aa6ae..35100f66 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -158,6 +158,7 @@
"remark" = "نام"
"protocol" = "پروتکل"
"port" = "پورت"
+"portMap" = "پورتهای نظیر"
"traffic" = "ترافیک"
"details" = "توضیحات"
"transportConfig" = "نحوه اتصال"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index bc36f046..21e87dd7 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -158,6 +158,7 @@
"remark" = "Catatan"
"protocol" = "Protokol"
"port" = "Port"
+"portMap" = "Port Mapping"
"traffic" = "Traffic"
"details" = "Rincian"
"transportConfig" = "Transport"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index c4461fcf..4bc620c6 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -158,6 +158,7 @@
"remark" = "備考"
"protocol" = "プロトコル"
"port" = "ポート"
+"portMap" = "ポートマッピング"
"traffic" = "トラフィック"
"details" = "詳細情報"
"transportConfig" = "トランスポート設定"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index a59fb81c..a81f95fb 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -158,6 +158,7 @@
"remark" = "Observação"
"protocol" = "Protocolo"
"port" = "Porta"
+"portMap" = "Porta Mapeada"
"traffic" = "Tráfego"
"details" = "Detalhes"
"transportConfig" = "Transporte"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 95ea9509..114ada89 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -158,6 +158,7 @@
"remark" = "Примечание"
"protocol" = "Протокол"
"port" = "Порт"
+"portMap" = "Порт-маппинг"
"traffic" = "Трафик"
"details" = "Подробнее"
"transportConfig" = "Транспорт"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index 95d27f2c..319b2a9e 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -158,6 +158,7 @@
"remark" = "Açıklama"
"protocol" = "Protokol"
"port" = "Port"
+"portMap" = "Port Atama"
"traffic" = "Trafik"
"details" = "Detaylar"
"transportConfig" = "Taşıma"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index 6847b8a0..46edca82 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -158,6 +158,7 @@
"remark" = "Примітка"
"protocol" = "Протокол"
"port" = "Порт"
+"portMap" = "Порт-перехід"
"traffic" = "Трафік"
"details" = "Деталі"
"transportConfig" = "Транспорт"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 4c3bd99e..c73dc383 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -158,6 +158,7 @@
"remark" = "Chú thích"
"protocol" = "Giao thức"
"port" = "Cổng"
+"portMap" = "Cổng tạo"
"traffic" = "Lưu lượng"
"details" = "Chi tiết"
"transportConfig" = "Giao vận"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index 6172c32c..ebe57f64 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -158,6 +158,7 @@
"remark" = "备注"
"protocol" = "协议"
"port" = "端口"
+"portMap" = "端口映射"
"traffic" = "流量"
"details" = "详细信息"
"transportConfig" = "传输配置"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index beb0c34d..694cd9f3 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -158,6 +158,7 @@
"remark" = "備註"
"protocol" = "協議"
"port" = "埠"
+"portMap" = "埠映射"
"traffic" = "流量"
"details" = "詳細資訊"
"transportConfig" = "傳輸配置"