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 @@ + + + + + + + + + + + - + + + 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" = "傳輸配置"