From 9419456fd2923c29138af809123e71dcf3660927 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 14:47:30 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=88=20Sync=202026-07-29=2014:47:30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-homeproxy/LICENSE | 340 +++ luci-app-homeproxy/Makefile | 22 +- luci-app-homeproxy/README.md | 68 + .../htdocs/luci-static/resources/homeproxy.js | 382 ++- .../resources/view/homeproxy/client.js | 1030 +++++++- .../resources/view/homeproxy/node.js | 789 ++++++- .../resources/view/homeproxy/server.js | 11 +- .../resources/view/homeproxy/status.js | 112 +- luci-app-homeproxy/po/templates/homeproxy.pot | 2036 +++++++++------- luci-app-homeproxy/po/zh_Hans/homeproxy.po | 2068 ++++++++++------- luci-app-homeproxy/root/etc/config/homeproxy | 41 +- .../etc/homeproxy/resources/china_ip4.txt | 996 ++++---- .../etc/homeproxy/resources/china_ip4.ver | 2 +- .../etc/homeproxy/resources/china_ip6.txt | 572 ++++- .../etc/homeproxy/resources/china_ip6.ver | 2 +- .../etc/homeproxy/resources/china_list.txt | 1874 +++++++++------ .../etc/homeproxy/resources/china_list.ver | 2 +- .../root/etc/homeproxy/resources/gfw_list.txt | 109 - .../root/etc/homeproxy/resources/gfw_list.ver | 2 +- .../etc/homeproxy/scripts/generate_client.uc | 566 ++++- .../etc/homeproxy/scripts/generate_server.uc | 2 +- .../root/etc/homeproxy/scripts/homeproxy.uc | 25 +- .../etc/homeproxy/scripts/migrate_config.uc | 173 +- .../etc/homeproxy/scripts/update_provider.sh | 46 + .../etc/homeproxy/scripts/update_resources.sh | 65 +- .../etc/homeproxy/scripts/update_rulesets.sh | 10 + .../homeproxy/scripts/update_subscriptions.uc | 1082 ++++++++- luci-app-homeproxy/root/etc/init.d/homeproxy | 99 +- .../root/etc/uci-defaults/luci-homeproxy | 30 + .../etc/uci-defaults/luci-homeproxy-migration | 0 .../share/rpcd/acl.d/luci-app-homeproxy.json | 7 +- .../root/usr/share/rpcd/ucode/luci.homeproxy | 1221 +++++++++- luci-app-pingpacket/README.md | 183 ++ luci-app-pingpacket/jpg/1.png | Bin 0 -> 144913 bytes luci-app-pingpacket/jpg/2.png | Bin 0 -> 84581 bytes luci-app-pingpacket/jpg/3.png | Bin 0 -> 121449 bytes .../luci-app-pingpacket/.gitattributes | 2 + .../luci-app-pingpacket/Makefile | 55 + .../luasrc/controller/pingpacket.lua | 518 +++++ .../luasrc/view/pingpacket/logs.htm | 477 ++++ .../luasrc/view/pingpacket/status.htm | 1375 +++++++++++ .../po/zh_Hans/pingpacket.po | 81 + .../root/etc/config/pingpacket | 9 + .../root/etc/init.d/pingpacket | 103 + .../root/usr/bin/pingpacket.sh | 682 ++++++ .../share/rpcd/acl.d/luci-app-pingpacket.json | 15 + luci-app-qemu-vms/Makefile | 4 +- qemu-vms/Makefile | 12 +- 48 files changed, 13901 insertions(+), 3399 deletions(-) create mode 100644 luci-app-homeproxy/LICENSE create mode 100644 luci-app-homeproxy/README.md mode change 100644 => 100755 luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc create mode 100644 luci-app-homeproxy/root/etc/homeproxy/scripts/update_provider.sh create mode 100644 luci-app-homeproxy/root/etc/homeproxy/scripts/update_rulesets.sh mode change 100644 => 100755 luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy mode change 100644 => 100755 luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration create mode 100644 luci-app-pingpacket/README.md create mode 100644 luci-app-pingpacket/jpg/1.png create mode 100644 luci-app-pingpacket/jpg/2.png create mode 100644 luci-app-pingpacket/jpg/3.png create mode 100644 luci-app-pingpacket/luci-app-pingpacket/.gitattributes create mode 100644 luci-app-pingpacket/luci-app-pingpacket/Makefile create mode 100644 luci-app-pingpacket/luci-app-pingpacket/luasrc/controller/pingpacket.lua create mode 100644 luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/logs.htm create mode 100644 luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/status.htm create mode 100644 luci-app-pingpacket/luci-app-pingpacket/po/zh_Hans/pingpacket.po create mode 100644 luci-app-pingpacket/luci-app-pingpacket/root/etc/config/pingpacket create mode 100644 luci-app-pingpacket/luci-app-pingpacket/root/etc/init.d/pingpacket create mode 100644 luci-app-pingpacket/luci-app-pingpacket/root/usr/bin/pingpacket.sh create mode 100644 luci-app-pingpacket/luci-app-pingpacket/root/usr/share/rpcd/acl.d/luci-app-pingpacket.json diff --git a/luci-app-homeproxy/LICENSE b/luci-app-homeproxy/LICENSE new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/luci-app-homeproxy/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/luci-app-homeproxy/Makefile b/luci-app-homeproxy/Makefile index 2f34e39f6..2f704d3dd 100644 --- a/luci-app-homeproxy/Makefile +++ b/luci-app-homeproxy/Makefile @@ -1,15 +1,31 @@ # SPDX-License-Identifier: GPL-2.0-only # -# Copyright (C) 2022-2024 ImmortalWrt.org +# Copyright (C) 2022-2023 ImmortalWrt.org include $(TOPDIR)/rules.mk -LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64 +PKG_NAME:=luci-app-homeproxy +PKG_VERSION:=2.0.0 +PKG_RELEASE:=8 + +LUCI_TITLE:=Sing-box proxy management platform for OpenWrt +LUCI_PKGARCH:=all LUCI_DEPENDS:= \ +sing-box \ +firewall4 \ + +dnsmasq-full \ + +ip-full \ + +kmod-tun \ +kmod-nft-tproxy \ - +ucode-mod-digest + +ca-bundle \ + +curl \ + +jsonfilter \ + +unzip \ + +ucode-mod-digest \ + +ucode-mod-fs \ + +ucode-mod-math \ + +ucode-mod-ubus \ + +ucode-mod-uci define Package/luci-app-homeproxy/conffiles /etc/config/homeproxy diff --git a/luci-app-homeproxy/README.md b/luci-app-homeproxy/README.md new file mode 100644 index 000000000..e1b1f3adf --- /dev/null +++ b/luci-app-homeproxy/README.md @@ -0,0 +1,68 @@ +适用于 OpenWrt 24.10 的 HomeProxy LuCI 管理界面,按 `官方内核sing-box 1.13.13` 进行二次定制适配,在官方内核基础上增加功能: +1、远程规则集本地持久化 +2、Clash API 代理供应商 +3、Clash API 中文名称 + +## 一、安装 +上传 zip 到 OpenWrt /tmp目录后解压并执行(文件名homeproxy-2.0.1-x86_64.zip自行替换): +```sh +cd /tmp +unzip homeproxy-2.0.1-x86_64.zip +cd homeproxy-2.0.1-x86_64 +sh install.sh +``` + +## 二、卸载 +```sh +/etc/init.d/homeproxy stop 2>/dev/null || true +/etc/init.d/homeproxy disable 2>/dev/null || true +opkg remove luci-i18n-homeproxy-zh-cn 2>/dev/null || true +opkg remove luci-app-homeproxy 2>/dev/null || true +rm -rf /etc/config/homeproxy +rm -rf /etc/homeproxy +rm -rf /usr/share/homeproxy +rm -rf /www/luci-static/resources/view/homeproxy +rm -f /www/luci-static/resources/homeproxy.js +rm -f /usr/share/luci/menu.d/luci-app-homeproxy.json +rm -f /usr/share/rpcd/acl.d/luci-app-homeproxy.json +rm -f /usr/share/rpcd/ucode/luci.homeproxy +rm -f /tmp/luci-indexcache +rm -rf /tmp/luci-modulecache/* +/etc/init.d/rpcd restart +/etc/init.d/uhttpd restart +``` + +## 三、维护工作流 +- `Rescan-Translation.yml`:代码变更后自动重新扫描并更新翻译文件,也支持手动运行。 +- `Update-Geodata.yml`:每周自动更新内置 geodata 资源,也支持手动运行。 + +## 四、常见问题 +1、面版下载失败解决办法 +第一种:面版设置-UI下载地址,下拉选择需要的UI,点击更新面版手动触发面版下载。 +第二种:下载面版ZIP包,手动上传面版ZIP,下载地址如下。 +镜像 +```sh +https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip +``` +或者直连 +```sh +https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip +``` +2、远程规则集是否下载成功判断 +连接SSH查看/etc/homeproxy/ruleset/目录下是否有规则集文件,有则表示下载成功,反之则没下载成功,检查规则集远程连接。 + +3、安装新包后如果页面仍显示旧文字,建议清浏览器缓存,或执行: +```sh +rm -f /tmp/luci-indexcache +rm -rf /tmp/luci-modulecache/* +/etc/init.d/rpcd restart +/etc/init.d/uhttpd restart +``` + +## 五、感谢作者 +immortalwrt +https://github.com/immortalwrt/homeproxy + +VIKINGYFY +https://github.com/VIKINGYFY/homeproxy + diff --git a/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js b/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js index 369b3a4c0..798b729c6 100644 --- a/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js +++ b/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js @@ -12,6 +12,158 @@ 'require uci'; 'require ui'; +function pad2(value) { + value = (value == null) ? '' : String(value).trim(); + return value.length === 1 ? '0' + value : value; +} + +function normalizeTimePart(value, min, max) { + value = parseInt(String(value || '').trim(), 10); + if (isNaN(value)) + value = min; + value = Math.max(min, Math.min(max, value)); + return pad2(value); +} + +function parseCron(value) { + const matched = String(value || '').trim().match(/^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+([0-7*])$/); + if (!matched) + return null; + + const minute = +matched[1], + hour = +matched[2]; + if (minute < 0 || minute > 59 || hour < 0 || hour > 23) + return null; + + return { + minute: normalizeTimePart(matched[1], 0, 59), + hour: normalizeTimePart(matched[2], 0, 23), + day: matched[3] === '*' ? '*' : (matched[3] === '7' ? '0' : matched[3]) + }; +} + +function buildCron(day, hour, minute) { + return [ + normalizeTimePart(minute, 0, 59), + normalizeTimePart(hour, 0, 23), + '*', + '*', + String(day || '*') + ].join(' '); +} + +function alignCronEditorRow(wrap) { + let field = wrap?.parentNode; + + while (field && !field.classList?.contains('cbi-value-field')) + field = field.parentNode; + + let row = field?.parentNode; + while (row && !row.classList?.contains('cbi-value')) + row = row.parentNode; + + if (!row || !field) + return false; + + if (wrap._dailyRow) { + wrap._dailyRow.style.display = row.style.display; + return true; + } + + let dailyRow = E('div', { 'class': 'cbi-value homeproxy-cron-daily-row' }, [ + E('div', { 'class': 'cbi-value-title' }, _('Update time (daily)')), + E('div', { 'class': 'cbi-value-field' }, [ wrap._dailyField ]) + ]); + + wrap._dailyRow = dailyRow; + row.parentNode.insertBefore(dailyRow, row.nextSibling); + + return true; +} + +function watchCronEditorRow(wrap) { + const align = () => alignCronEditorRow(wrap); + [ 0, 50, 200, 500, 1000, 2000 ].forEach(delay => window.setTimeout(align, delay)); + + if (typeof MutationObserver !== 'undefined' && document?.body) { + const observer = new MutationObserver(() => { + if (align()) + observer.disconnect(); + }); + + observer.observe(document.body, { childList: true, subtree: true }); + window.setTimeout(() => observer.disconnect(), 3000); + } +} + +function renderCronEditor(input) { + if (!input) + return null; + + const parsed = parseCron(input.value); + input.type = 'text'; + input.readOnly = true; + input.tabIndex = -1; + input.style.position = 'absolute'; + input.style.opacity = '0'; + input.style.pointerEvents = 'none'; + input.style.width = '1px'; + input.style.height = '1px'; + input.style.minWidth = '1px'; + input.style.maxWidth = '1px'; + input.style.padding = '0'; + input.style.border = '0'; + + let day = E('select', { 'class': 'cbi-input-select', 'style': 'width: 12em !important; min-width: 12em !important; max-width: 12em !important; box-sizing: border-box;' }), + hour = E('select', { 'class': 'cbi-input-select', 'style': 'width: 4.25em !important; min-width: 4.25em !important; max-width: 4.25em !important; box-sizing: border-box;' }), + minute = E('select', { 'class': 'cbi-input-select', 'style': 'width: 4.25em !important; min-width: 4.25em !important; max-width: 4.25em !important; box-sizing: border-box;' }), + dailyField = E('div', { 'style': 'display: flex; align-items: center; gap: .25em; width: 9.5em;' }, [ + hour, + E('span', ':'), + minute + ]), + wrap = E('div', { 'class': 'homeproxy-cron-editor' }, [ day ]); + + wrap._dailyField = dailyField; + + for (let i = 0; i < 24; i++) + hour.appendChild(E('option', { value: pad2(i) }, pad2(i))); + + for (let i = 0; i < 60; i++) + minute.appendChild(E('option', { value: pad2(i) }, pad2(i))); + + [ + ['*', _('Every day')], + ['1', _('Every Monday')], + ['2', _('Every Tuesday')], + ['3', _('Every Wednesday')], + ['4', _('Every Thursday')], + ['5', _('Every Friday')], + ['6', _('Every Saturday')], + ['0', _('Every Sunday')] + ].forEach(([value, label]) => day.appendChild(E('option', { value }, label))); + + day.value = parsed ? parsed.day : '*'; + hour.value = parsed ? parsed.hour : '00'; + minute.value = parsed ? parsed.minute : '00'; + input.value = buildCron(day.value, hour.value, minute.value); + input.setAttribute('value', input.value); + + const sync = () => { + input.value = buildCron(day.value, hour.value, minute.value); + input.setAttribute('value', input.value); + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }; + + day.addEventListener('change', sync); + hour.addEventListener('change', sync); + minute.addEventListener('change', sync); + wrap.appendChild(input); + window.setTimeout(() => watchCronEditorRow(wrap), 0); + return wrap; +} + return baseclass.extend({ dns_strategy: { '': _('Default'), @@ -86,6 +238,29 @@ return baseclass.extend({ } }), + CBIMultiValue: form.MultiValue.extend({ + __name__: 'CBI.HomeProxyMultiValue', + + renderWidget(section_id, _option_index, cfgvalue) { + let value = (cfgvalue != null) ? cfgvalue : this.default, + choices = this.transformChoices() || {}, + widget = new ui.Dropdown(L.toArray(value), choices, { + id: this.cbid(section_id), + sort: this.keylist, + multiple: true, + optional: this.optional || this.rmempty, + select_placeholder: this.placeholder, + create: this.create, + display_items: this.display_size ?? this.size ?? 3, + dropdown_items: this.dropdown_size ?? this.size ?? -1, + validate: this.getValidator(section_id), + disabled: (this.readonly != null) ? this.readonly : this.map.readonly + }); + + return widget.render(); + } + }), + calcStringMD5(e) { /* Thanks to https://stackoverflow.com/a/41602636 */ let h = (a, b) => { @@ -185,6 +360,49 @@ return baseclass.extend({ return L.resolveDefault(callGetSingBoxFeatures(), {}); }, + renderCronSelector(/* ... */) { + if (!this._homeproxyCronOriginalFormvalue) { + this._homeproxyCronOriginalFormvalue = this.formvalue; + this.formvalue = function(section_id) { + let cbid = this.cbid(section_id), + ids = [ cbid, 'widget.' + cbid ], + roots = [ document.getElementById('modal_overlay'), document ], + input = null; + + for (let root of roots) { + if (!root) + continue; + + for (let el of root.querySelectorAll('input')) { + if (ids.includes(el.id) || ids.includes(el.name)) { + input = el; + break; + } + } + + if (input) + break; + } + + let value = input?.value || + this._homeproxyCronOriginalFormvalue?.call(this, section_id) || + this.default || + '0 0 * * *'; + + return parseCron(value) ? value : (this.default || '0 0 * * *'); + }; + } + + let node = form.Value.prototype.renderWidget.apply(this, arguments), + editor = renderCronEditor(node.querySelector('input')); + + return editor || node; + }, + + renderCronSelectorRow(/* ... */) { + return form.Value.prototype.render.apply(this, arguments); + }, + generateRand(type, length) { let byteArr; if (['base64', 'hex'].includes(type)) @@ -208,6 +426,9 @@ return baseclass.extend({ }, loadDefaultLabel(uciconfig, ucisection) { + if (!ucisection) + return ''; + let label = uci.get(uciconfig, ucisection, 'label'); if (label) { return label; @@ -218,32 +439,127 @@ return baseclass.extend({ }, loadModalTitle(title, addtitle, uciconfig, ucisection) { + if (!ucisection) + return addtitle; + let label = uci.get(uciconfig, ucisection, 'label'); return label ? title + ' » ' + label : addtitle; }, + normalizeSectionId(label, prefix) { + let section_id = (label || '').trim().replace(/[^A-Za-z0-9_]/g, '_').replace(/^_+|_+$/g, ''); + return (prefix || 'cfg') + '_' + (section_id || 'section'); + }, + renderSectionAdd(section, extra_class) { let el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]), - nameEl = el.querySelector('.cbi-section-create-name'); - ui.addValidator(nameEl, 'uciname', true, (v) => { - let button = el.querySelector('.cbi-section-create > .cbi-button-add'); - let uciconfig = section.uciconfig || section.map.config; + nameEl = el.querySelector('.cbi-section-create-name'), + button = el.querySelector('.cbi-section-create > .cbi-button-add'), + uciconfig = section.uciconfig || section.map.config, + sectiontype = section.sectiontype, + labelEl = E('input', { + 'type': 'text', + 'class': nameEl.className + }), + hintEl = E('div', { 'class': 'cbi-value-description' }); - if (!v) { + nameEl.style.display = 'none'; + nameEl.parentNode.insertBefore(labelEl, nameEl); + nameEl.parentNode.appendChild(hintEl); + button.disabled = true; + + const syncSectionName = () => { + let label = (labelEl.value || '').trim(); + hintEl.textContent = ''; + + if (!label) { + nameEl.value = ''; button.disabled = true; - return true; - } else if (uci.get(uciconfig, v)) { - button.disabled = true; - return _('Expecting: %s').format(_('unique UCI identifier')); - } else { - button.disabled = null; - return true; + return; } - }, 'blur', 'keyup'); + + let duplicate = false; + uci.sections(uciconfig, sectiontype, (res) => { + if ((res.label || res['.name']) === label) + duplicate = true; + }); + + if (duplicate) { + nameEl.value = ''; + button.disabled = true; + hintEl.textContent = _('Expecting: %s').format(_('unique value')); + return; + } + + let normalized = (label || '').trim().replace(/[^A-Za-z0-9_]/g, '_').replace(/^_+|_+$/g, '') || 'section', + section_id = (sectiontype || 'cfg') + '_' + normalized, + suffix = 1; + while (uci.get(uciconfig, section_id)) + section_id = (sectiontype || 'cfg') + '_' + normalized + '_' + suffix++; + + nameEl.value = section_id; + nameEl.dataset.sectionId = section_id; + nameEl.dataset.sectionLabel = label; + button.disabled = null; + }; + + labelEl.addEventListener('input', syncSectionName); + labelEl.addEventListener('blur', syncSectionName); + + button.addEventListener('click', () => { + syncSectionName(); + + let label = nameEl.dataset.sectionLabel, + section_id = nameEl.dataset.sectionId; + + window.setTimeout(() => { + if (label && section_id && uci.get(uciconfig, section_id)) + uci.set(uciconfig, section_id, 'label', label); + }, 0); + }); return el; }, + installCloseButtonText() { + if (window.__homeproxyCloseButtonText) + return; + + window.__homeproxyCloseButtonText = true; + + let fix = (root) => { + for (let el of (root || document).querySelectorAll('button, .btn, input[type="button"], input[type="submit"]')) { + let text = (el.textContent || el.value || '').trim(); + if (text !== 'Dismiss' && text !== '\u5ffd\u7565') + continue; + + if ('value' in el && !el.textContent.trim()) + el.value = _('Close'); + else + el.textContent = _('Close'); + } + }; + + let observe = () => { + if (!document.body) + return; + + fix(document); + new MutationObserver((mutations) => { + for (let mutation of mutations) + for (let node of mutation.addedNodes) + if (node.nodeType === 1) + fix(node); + fix(document); + }).observe(document.body, { childList: true, subtree: true }); + }; + + if (document.body) + observe(); + else + document.addEventListener('DOMContentLoaded', observe, { once: true }); + }, + uploadCertificate(_option, type, filename, ev) { const callWriteCertificate = rpc.declare({ object: 'luci.homeproxy', @@ -264,6 +580,44 @@ return baseclass.extend({ .catch((e) => { ui.addNotification(null, E('p', e.message)) }); }, + uploadPanel(_option, ev) { + const callInstallPanel = rpc.declare({ + object: 'luci.homeproxy', + method: 'clash_api_install_panel', + expect: { '': {} } + }); + + const normalizeMessage = (message) => { + switch (message) { + case 'panel_backup_failed': + return _('Backup panel failed.'); + case 'panel_restore_failed': + return _('Restore panel failed.'); + case 'empty_panel_zip': + return _('Panel ZIP file is empty.'); + case 'unzip_unavailable': + return _('The unzip command is unavailable.'); + case 'invalid_panel_zip': + return _('Invalid panel ZIP file.'); + case 'panel_install_failed': + return _('Install panel failed.'); + default: + return message || _('unknown error'); + } + }; + + return ui.uploadFile('/tmp/homeproxy_panel.tmp', ev.target) + .then(L.bind((_btn, res) => { + return L.resolveDefault(callInstallPanel(), {}).then((ret) => { + if (ret.result === true) + ui.addNotification(null, E('p', _('Your %s was successfully uploaded. Size: %sB.').format(_('panel ZIP package'), res.size)), 'info'); + else + ui.addNotification(null, E('p', _('Failed to upload %s, error: %s.').format(_('panel ZIP package'), normalizeMessage(ret.error))), 'danger'); + }); + }, this, ev.target)) + .catch((e) => { ui.addNotification(null, E('p', e.message || e), 'danger') }); + }, + validateBase64Key(length, section_id, value) { /* Thanks to luci-proto-wireguard */ if (section_id && value) @@ -304,7 +658,7 @@ return baseclass.extend({ if (section_id) { if (!value) return _('Expecting: %s').format(_('non-empty value')); - if (ucioption === 'node' && value === 'urltest') + if (ucioption === 'node' && (value === 'urltest' || value === 'selector')) return true; let duplicate = false; diff --git a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js index f012fddf5..8d9226b0c 100644 --- a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js +++ b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js @@ -10,6 +10,7 @@ 'require poll'; 'require rpc'; 'require uci'; +'require ui'; 'require validation'; 'require view'; @@ -24,6 +25,13 @@ const callServiceList = rpc.declare({ expect: { '': {} } }); +const callRCInit = rpc.declare({ + object: 'rc', + method: 'init', + params: ['name', 'action'], + expect: { '': {} } +}); + const callReadDomainList = rpc.declare({ object: 'luci.homeproxy', method: 'acllist_read', @@ -38,6 +46,89 @@ const callWriteDomainList = rpc.declare({ expect: { '': {} } }); +const callCurrentNode = rpc.declare({ + object: 'luci.homeproxy', + method: 'current_node_get', + expect: { '': {} } +}); + +const callPackageVersion = rpc.declare({ + object: 'luci.homeproxy', + method: 'package_get_version', + expect: { '': {} } +}); + +const callUpdatePanel = rpc.declare({ + object: 'luci.homeproxy', + method: 'clash_api_update_panel', + expect: { '': {} } +}); + +const statusCss = ` +#homeproxy_status_panel { + margin-bottom: 1rem; +} +#homeproxy_status_panel .homeproxy-status-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)) auto; + gap: 10px; + align-items: center; +} +#homeproxy_status_panel .homeproxy-status-field { + min-width: 0; +} +#homeproxy_status_panel .homeproxy-status-label { + font-weight: 600; + text-align: center; + margin-bottom: 6px; +} +#homeproxy_status_panel input { + width: 100%; + min-width: 0; +} +#homeproxy_status_panel .homeproxy-version-field input { + width: 100%; +} +#homeproxy_status_panel .homeproxy-core-version-field input { + width: 100%; +} +#homeproxy_status_panel .homeproxy-core-status-field input { + width: 100%; + text-align: center; +} +#homeproxy_status_panel .homeproxy-core-status { + border: unset; + font-style: italic; + font-weight: 700; +} +#homeproxy_status_panel .homeproxy-status-actions { + display: flex; + flex-wrap: nowrap; + gap: 6px; + align-items: center; + justify-content: flex-end; + min-width: 0; +} +#homeproxy_status_panel .homeproxy-status-actions .btn { + width: auto; + min-width: 0; + padding-left: 6px; + padding-right: 6px; + white-space: nowrap; +} +@media (max-width: 700px) { + #homeproxy_status_panel .homeproxy-status-grid { + grid-template-columns: 1fr; + } + #homeproxy_status_panel .homeproxy-status-actions { + flex-wrap: wrap; + justify-content: flex-start; + } + #homeproxy_status_panel .homeproxy-status-actions .btn { + flex: 1 1 auto; + } +}`; + function getServiceStatus() { return L.resolveDefault(callServiceList('homeproxy'), {}).then((res) => { let isRunning = false; @@ -48,15 +139,253 @@ function getServiceStatus() { }); } -function renderStatus(isRunning, version) { - let spanTemp = '%s (sing-box v%s) %s'; - let renderHTML; - if (isRunning) - renderHTML = spanTemp.format('green', _('HomeProxy'), version, _('RUNNING')); - else - renderHTML = spanTemp.format('red', _('HomeProxy'), version, _('NOT RUNNING')); +function updateCoreStatus(isRunning, currentNode) { + let status = document.getElementById('homeproxy_core_status'); + if (!status) + return; - return renderHTML; + status.style.color = isRunning ? 'green' : 'red'; + status.value = currentNode ? _('Running: %s').format(currentNode) : (isRunning ? _('Running') : _('Not Running')); +} + +function parseControllerPort(externalController) { + let value = externalController || '127.0.0.1:9090'; + let idx = value.lastIndexOf(':'); + return (idx >= 0) ? value.substring(idx + 1) : '9090'; +} + +function getClashApiInfo() { + let port = parseControllerPort(uci.get('homeproxy', 'clash_api', 'external_controller')), + secret = uci.get('homeproxy', 'clash_api', 'secret') || ''; + + return { + port, + secret, + baseUrl: 'http://' + window.location.hostname + ':' + port + }; +} + +function updateDashboard() { + return callUpdatePanel().then((res) => { + if (!res?.result) { + let message = res?.error; + if (message === 'update_panel_failed') + message = _('Update panel failed.'); + else if (message === 'clash_api_unavailable') + message = _('Clash API unavailable.'); + else if (message === 'panel_backup_failed') + message = _('Backup panel failed.'); + else if (message === 'panel_restore_failed') + message = _('Restore panel failed.'); + + throw new Error(message || _('Update failed.')); + } + ui.addNotification(null, E('p', _('Successfully updated.')), 'info'); + }).catch((err) => { + ui.addNotification(null, E('p', err.message || err), 'danger'); + }); +} + +function openDashboard() { + let api = getClashApiInfo(); + let query = new URLSearchParams({ + host: window.location.hostname, + hostname: window.location.hostname, + port: api.port, + secret: api.secret + }).toString(); + + window.open(api.baseUrl + '/ui/?' + query, '_blank'); + return Promise.resolve(); +} + +function normalizeRuleSetSectionId(tag) { + return 'ruleset_' + tag.replace(/[^A-Za-z0-9_]/g, '_').replace(/^_+|_+$/g, ''); +} + +function getDnsServerDefaultPort(type) { + switch (type) { + case 'udp': + case 'tcp': + return '53'; + case 'tls': + case 'quic': + return '853'; + case 'https': + case 'h3': + return '443'; + default: + return 'auto'; + } +} + +function getDnsServerAddressPlaceholder(type) { + switch (type) { + case 'https': + case 'h3': + return ''; + case 'tls': + case 'quic': + return 'dns.alidns.com'; + default: + return ''; + } +} + +function validateDnsServerAddress(type, value) { + if (!value) + return false; + + if (value.includes('://')) { + if (!['https', 'h3'].includes(type)) + return false; + + try { + let url = new URL(value); + if (url.protocol !== 'https:') + return false; + + value = url.hostname; + } catch (e) { + return false; + } + } + + return stubValidator.apply('hostname', value) || + stubValidator.apply('ip4addr', value) || + stubValidator.apply('ip6addr', value.match(/^\[(.+)\]$/)?.[1] || value); +} + +function validateDuration(value) { + return !value || /^[1-9]\d*(ms|s|m|h|d)$/.test(value); +} + +function validateUpdateCron(value) { + if (!value) + return true; + + const matched = String(value).trim().match(/^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+([0-7*])$/); + if (!matched) + return false; + + return +matched[1] <= 59 && +matched[2] <= 23; +} + +function translateKnownError(error) { + let name = error?.name || '', + message = error?.message || error || ''; + + if (name === 'TypeError' && String(message).match(/can't convert null to object|Cannot convert undefined or null to object/i)) + return _('Type error') + '\n' + _('Cannot convert null to object.'); + + return null; +} + +function installKnownErrorTranslator() { + if (window.__homeproxyKnownErrorTranslator) + return; + + window.__homeproxyKnownErrorTranslator = true; + + window.addEventListener('error', (ev) => { + let message = translateKnownError(ev.error || ev.message); + if (message) { + ev.preventDefault(); + ui.addNotification(null, E('p', message), 'danger'); + } + }, true); + + window.addEventListener('unhandledrejection', (ev) => { + let message = translateKnownError(ev.reason); + if (message) { + ev.preventDefault(); + ui.addNotification(null, E('p', message), 'danger'); + } + }, true); +} + +function renderRuleSetAdd(section, extra_class) { + let el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]), + nameEl = el.querySelector('.cbi-section-create-name'), + button = el.querySelector('.cbi-section-create > .cbi-button-add'), + uciconfig = section.uciconfig || section.map.config; + + let tagEl = E('input', { + 'type': 'text', + 'class': nameEl.className + }); + let hintEl = E('div', { 'class': 'cbi-value-description' }); + + nameEl.style.display = 'none'; + nameEl.parentNode.insertBefore(tagEl, nameEl); + nameEl.parentNode.appendChild(hintEl); + button.disabled = true; + + let syncSectionName = () => { + let tag = (tagEl.value || '').trim(); + hintEl.textContent = ''; + + if (!tag) { + nameEl.value = ''; + button.disabled = true; + return; + } + + if (!tag.match(/^[A-Za-z0-9_.-]+$/)) { + nameEl.value = ''; + button.disabled = true; + hintEl.textContent = _('Expecting: %s').format(_('valid tag')); + return; + } + + let duplicate = false; + uci.sections(uciconfig, 'ruleset', (res) => { + if ((res.tag || ('cfg-' + res['.name'] + '-rule')) === tag) + duplicate = true; + }); + + if (duplicate) { + nameEl.value = ''; + button.disabled = true; + hintEl.textContent = _('Expecting: %s').format(_('unique value')); + return; + } + + let section_id = normalizeRuleSetSectionId(tag), + suffix = 1; + while (uci.get(uciconfig, section_id)) + section_id = normalizeRuleSetSectionId(tag) + '_' + suffix++; + + nameEl.value = section_id; + nameEl.dataset.sectionId = section_id; + nameEl.dataset.ruleSetTag = tag; + button.disabled = null; + }; + + tagEl.addEventListener('input', syncSectionName); + tagEl.addEventListener('blur', syncSectionName); + + button.addEventListener('click', () => { + syncSectionName(); + + let tag = nameEl.dataset.ruleSetTag, + section_id = nameEl.dataset.sectionId; + + window.setTimeout(() => { + if (tag && section_id && uci.get(uciconfig, section_id)) { + uci.set(uciconfig, section_id, 'tag', tag); + uci.set(uciconfig, section_id, 'label', tag); + uci.set(uciconfig, section_id, 'enabled', '1'); + uci.set(uciconfig, section_id, 'type', 'remote'); + uci.set(uciconfig, section_id, 'format', 'binary'); + uci.set(uciconfig, section_id, 'remote_path', '/etc/homeproxy/ruleset/'); + uci.set(uciconfig, section_id, 'auto_update', '1'); + uci.set(uciconfig, section_id, 'update_interval', '0 0 * * *'); + } + }, 0); + }); + + return el; } let stubValidator = { @@ -77,18 +406,29 @@ return view.extend({ return Promise.all([ uci.load('homeproxy'), hp.getBuiltinFeatures(), - network.getHostHints() + network.getHostHints(), + L.resolveDefault(callPackageVersion(), {}) ]); }, render(data) { let m, s, o, ss, so; + installKnownErrorTranslator(); + hp.installCloseButtonText(); + let features = data[1], - hosts = data[2]?.hosts; + hosts = data[2]?.hosts, + packageVersion = data[3]?.version || '-', + routingMode = uci.get(data[0], 'config', 'routing_mode'); /* Cache all configured proxy nodes, they will be called multiple times */ let proxy_nodes = {}; + let subscription_groups = {}; + let routing_groups = {}; + let normalizeFormList = function(value) { + return Array.isArray(value) ? value : (value ? [value] : []); + }; uci.sections(data[0], 'node', (res) => { let nodeaddr = ((res.type === 'direct') ? res.override_address : res.address) || '', nodeport = ((res.type === 'direct') ? res.override_port : res.port) || ''; @@ -97,21 +437,118 @@ return view.extend({ String.format('[%s] %s', res.type, res.label || ((stubValidator.apply('ip6addr', nodeaddr) ? String.format('[%s]', nodeaddr) : nodeaddr) + ':' + nodeport)); }); + let subscriptionUrls = normalizeFormList(uci.get(data[0], 'subscription', 'subscription_url')), + subscriptionNames = normalizeFormList(uci.get(data[0], 'subscription', 'subscription_name')); + for (let i = 0; i < subscriptionUrls.length; i++) { + let url = subscriptionUrls[i], + title = String(subscriptionNames[i] || '').trim(); + + if (title) + subscription_groups[hp.calcStringMD5(url.replace(/#.*$/, ''))] = title; + } + uci.sections(data[0], 'routing_node', (res) => { + routing_groups[res['.name']] = res.label || res['.name']; + }); + let collectSelectedRoutingNodes = function(groups, subscription_nodes, selected_nodes, policy_nodes, section_id) { + let nodes = []; + groups = normalizeFormList(groups); + subscription_nodes = normalizeFormList(subscription_nodes); + selected_nodes = normalizeFormList(selected_nodes); + policy_nodes = normalizeFormList(policy_nodes); + + uci.sections(data[0], 'node', (res) => { + if (res.grouphash && groups.includes(res.grouphash) && !nodes.includes(res['.name'])) + nodes.push(res['.name']); + }); + for (let node of subscription_nodes) + if (proxy_nodes[node] && !nodes.includes(node)) + nodes.push(node); + for (let node of selected_nodes) + if (proxy_nodes[node] && !nodes.includes(node)) + nodes.push(node); + for (let node of policy_nodes) + if (node !== section_id && routing_groups[node] && !nodes.includes(node)) + nodes.push(node); + + return nodes; + }; m = new form.Map('homeproxy', _('HomeProxy'), - _('The modern ImmortalWrt proxy platform for ARM64/AMD64.')); + _('OpenWrt-designed Sing-box proxy management platform. It is recommended to disable Dnsmasq DNS redirection.')); s = m.section(form.TypedSection); s.render = function () { poll.add(function () { - return L.resolveDefault(getServiceStatus()).then((res) => { - let view = document.getElementById('service_status'); - view.innerHTML = renderStatus(res, features.version); - }); - }); + return Promise.all([ + L.resolveDefault(getServiceStatus(), false), + L.resolveDefault(callCurrentNode(), null) + ]).then((res) => { + let isRunning = res[0], + current = res[1], + current_label = null; - return E('div', { class: 'cbi-section', id: 'status_bar' }, [ - E('p', { id: 'service_status' }, _('Collecting data...')) + if (current?.mode === 'urltest') { + let active = current.active || {}; + let nodeName = (active?.id && active.id !== 'urltest') ? (proxy_nodes[active.id] || active.label || active.id) : _('Invalid node'); + + current_label = _('URLTest: %s').format(nodeName); + } + + updateCoreStatus(isRunning, current_label); + }); + }); + + let actions = [ + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, () => callRCInit('homeproxy', 'reload')) + }, [ _('Reload Service') ]), + E('button', { + 'class': 'btn cbi-button cbi-button-negative', + 'click': ui.createHandlerFn(this, () => callRCInit('homeproxy', 'restart')) + }, [ _('Restart Service') ]) + ]; + + if (routingMode === 'custom') { + actions.push( + E('button', { + 'class': 'btn cbi-button cbi-button-positive', + 'click': ui.createHandlerFn(this, updateDashboard) + }, [ _('Update Panel') ]), + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, (ev) => hp.uploadPanel(null, ev)) + }, [ _('Upload Panel ZIP') ]), + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, openDashboard) + }, [ _('Open Panel') ]) + ); + } + + return E('div', { class: 'cbi-section', id: 'homeproxy_status_panel' }, [ + E('style', [ statusCss ]), + E('h3', _('Status')), + E('div', { class: 'homeproxy-status-grid' }, [ + E('div', { class: 'homeproxy-status-field homeproxy-version-field' }, [ + E('div', { class: 'homeproxy-status-label' }, _('Plugin Version')), + E('input', { class: 'cbi-input-text', readonly: '', value: packageVersion }) + ]), + E('div', { class: 'homeproxy-status-field homeproxy-core-version-field' }, [ + E('div', { class: 'homeproxy-status-label' }, _('Core Version')), + E('input', { class: 'cbi-input-text', readonly: '', value: features.version || '-' }) + ]), + E('div', { class: 'homeproxy-status-field homeproxy-core-status-field' }, [ + E('div', { class: 'homeproxy-status-label' }, _('Core Status')), + E('input', { + id: 'homeproxy_core_status', + class: 'cbi-input-text homeproxy-core-status', + readonly: '', + value: _('Collecting data...') + }) + ]), + E('div', { class: 'homeproxy-status-actions' }, actions) + ]) ]); } @@ -153,7 +590,7 @@ return view.extend({ o.value('urltest', _('URLTest')); for (let i in proxy_nodes) o.value(i, proxy_nodes[i]); - o.default = 'nil'; + o.default = 'same'; o.depends({'routing_mode': /^((?!custom).)+$/, 'proxy_mode': /^((?!redirect$).)+$/}); o.rmempty = false; @@ -179,14 +616,13 @@ return view.extend({ o = s.taboption('routing', form.Value, 'dns_server', _('DNS server'), _('Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified.')); o.value('wan', _('WAN DNS (read from interface)')); - o.value('1.1.1.1', _('CloudFlare Public DNS (1.1.1.1)')); - o.value('208.67.222.222', _('Cisco Public DNS (208.67.222.222)')); - o.value('8.8.8.8', _('Google Public DNS (8.8.8.8)')); - o.value('', '---'); - o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)')); - o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)')); - o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)')); - o.default = '8.8.8.8'; + o.value('https://dns.cloudflare.com/dns-query', _('CloudFlare Public DNS (DoH)')); + o.value('https://dns.google/dns-query', _('Google Public DNS (DoH)')); + o.value('https://dns.quad9.net/dns-query', _('Quad9 Public DNS (DoH)')); + o.value('https://dns.adguard-dns.com/dns-query', _('AdGuard Public DNS (DoH)')); + o.value('https://dns.sb/dns-query', _('DNS.SB Public DNS (DoH)')); + o.value('https://dns.opendns.com/dns-query', _('Cisco Public DNS (DoH)')); + o.default = 'https://dns.quad9.net/dns-query'; o.rmempty = false; o.depends({'routing_mode': 'custom', '!reverse': true}); o.validate = function(section_id, value) { @@ -217,12 +653,11 @@ return view.extend({ o = s.taboption('routing', form.Value, 'china_dns_server', _('China DNS server'), _('The dns server for resolving China domains. Support UDP, TCP, DoH, DoQ, DoT.')); o.value('wan', _('WAN DNS (read from interface)')); - o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)')); - o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)')); - o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)')); - o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)')); + o.value('https://doh-pure.onedns.net/dns-query', _('ThreatBook Public DNS (DoH)')); + o.value('https://doh.pub/dns-query', _('Tencent Public DNS (DoH)')); + o.value('https://dns.alidns.com/dns-query', _('AliYun Public DNS (DoH)')); o.depends('routing_mode', 'bypass_mainland_china'); - o.default = '223.5.5.5'; + o.default = 'https://dns.alidns.com/dns-query'; o.rmempty = false; o.validate = function(section_id, value) { if (section_id && !['wan'].includes(value)) { @@ -325,13 +760,6 @@ return view.extend({ desc.innerHTML = _('Less compatibility and sometimes better performance.'); } - so = ss.option(form.Flag, 'endpoint_independent_nat', _('Enable endpoint-independent NAT'), - _('Performance may degrade slightly, so it is not recommended to enable on when it is not needed.')); - so.default = so.enabled; - so.depends('tcpip_stack', 'mixed'); - so.depends('tcpip_stack', 'gvisor'); - so.rmempty = false; - so = ss.option(form.Value, 'udp_timeout', _('UDP NAT expiration time'), _('In seconds.')); so.datatype = 'uinteger'; @@ -354,7 +782,7 @@ return view.extend({ so.default = so.enabled; so.rmempty = false; - so = ss.option(form.ListValue, 'default_outbound', _('Default outbound'), + so = ss.option(form.ListValue, 'default_outbound', _('Default outbound (fallback)'), _('Default outbound for connections not matched by any routing rules.')); so.load = function(section_id) { delete this.keylist; @@ -418,11 +846,50 @@ return view.extend({ so = ss.option(form.ListValue, 'node', _('Node'), _('Outbound node')); - so.value('urltest', _('URLTest')); + so.value('urltest', _('Auto select')); + so.value('selector', _('Manual select')); for (let i in proxy_nodes) so.value(i, proxy_nodes[i]); - so.validate = L.bind(hp.validateUniqueValue, this, data[0], 'routing_node', 'node'); - so.editable = true; + so.default = 'selector'; + so.rmempty = false; + so.formvalue = function(section_id) { + let widget = this.getUIElement(section_id), + cbid = this.cbid(section_id), + overlay = document.getElementById('modal_overlay'), + input = null; + + if (overlay) + for (let el of overlay.querySelectorAll('input, select')) + if (el.id === cbid || el.name === cbid) { + input = el; + break; + } + + return widget?.getValue() || + input?.value || + uci.get(data[0], section_id, 'node') || + this.default || + 'selector'; + } + so.validate = function(section_id, value) { + value = value || this.formvalue(section_id); + + let result = hp.validateUniqueValue(data[0], 'routing_node', 'node', section_id, value); + if (result !== true) + return result; + + if (section_id && (value === 'urltest' || value === 'selector')) { + let groups = normalizeFormList(this.section.formvalue(section_id, 'subscription_groups')), + subscription_nodes = normalizeFormList(this.section.formvalue(section_id, 'subscription_nodes')), + selected = normalizeFormList(this.section.formvalue(section_id, 'selected_nodes')), + policy_nodes = normalizeFormList(this.section.formvalue(section_id, 'policy_nodes')); + + if (!groups.length && !subscription_nodes.length && !selected.length && !policy_nodes.length) + return _('Expecting: %s').format(_('non-empty value')); + } + + return true; + } so = ss.option(form.ListValue, 'domain_resolver', _('Domain resolver'), _('For resolving domain name in the server address.')); @@ -440,21 +907,21 @@ return view.extend({ return this.super('load', section_id); } - so.depends({'node': 'urltest', '!reverse': true}); + so.depends('node', /^((?!(urltest|selector)$).)+$/); so.modalonly = true; so = ss.option(form.ListValue, 'domain_strategy', _('Domain strategy'), _('The domain strategy for resolving the domain name in the address.')); for (let i in hp.dns_strategy) so.value(i, hp.dns_strategy[i]); - so.depends({'node': 'urltest', '!reverse': true}); + so.depends('node', /^((?!(urltest|selector)$).)+$/); so.modalonly = true; so = ss.option(widgets.DeviceSelect, 'bind_interface', _('Bind interface'), _('The network interface to bind to.')); so.multiple = false; so.noaliases = true; - so.depends({'outbound': '', 'node': /^((?!urltest$).)+$/}); + so.depends({'outbound': '', 'node': /^((?!(urltest|selector)$).)+$/}); so.modalonly = true; so = ss.option(form.ListValue, 'outbound', _('Outbound'), @@ -480,7 +947,15 @@ return view.extend({ if (res['.name'] !== section_id) { if (res.outbound === section_id && res['.name'] == value) conflict = true; - else if (res.node === 'urltest' && res.urltest_nodes?.includes(node) && res['.name'] == value) + else if (res.node === 'urltest' && normalizeFormList(res.urltest_nodes).includes(node) && res['.name'] == value) + conflict = true; + else if (res.node === 'urltest' && normalizeFormList(res.subscription_nodes).includes(node) && res['.name'] == value) + conflict = true; + else if (res.node === 'urltest' && normalizeFormList(res.selected_nodes).includes(node) && res['.name'] == value) + conflict = true; + else if (res.node === 'selector' && normalizeFormList(res.selected_nodes).includes(node) && res['.name'] == value) + conflict = true; + else if (res.node === 'selector' && normalizeFormList(res.subscription_nodes).includes(node) && res['.name'] == value) conflict = true; } }); @@ -490,21 +965,97 @@ return view.extend({ return true; } - so.depends({'node': 'urltest', '!reverse': true}); + so.depends('node', /^((?!(urltest|selector)$).)+$/); so.editable = true; - so = ss.option(hp.CBIStaticList, 'urltest_nodes', _('URLTest nodes'), - _('List of nodes to test.')); - for (let i in proxy_nodes) - so.value(i, proxy_nodes[i]); + so = ss.option(hp.CBIMultiValue, 'subscription_groups', _('Subscriptions')); + for (let hash in subscription_groups) + so.value(hash, subscription_groups[hash]); so.depends('node', 'urltest'); - so.validate = function(section_id) { - let value = this.section.formvalue(section_id, 'urltest_nodes'); - if (section_id && !value.length) - return _('Expecting: %s').format(_('non-empty value')); + so.depends('node', 'selector'); + so.rmempty = true; + so.modalonly = true; + + so = ss.option(hp.CBIMultiValue, 'subscription_nodes', _('Subscription nodes'), + _('List of nodes from subscriptions.')); + uci.sections(data[0], 'node', (res) => { + if (res.grouphash) + so.value(res['.name'], proxy_nodes[res['.name']]); + }); + so.depends('node', 'urltest'); + so.depends('node', 'selector'); + so.rmempty = true; + so.modalonly = true; + + so = ss.option(hp.CBIMultiValue, 'selected_nodes', _('Custom nodes')); + uci.sections(data[0], 'node', (res) => { + if (!res.grouphash) + so.value(res['.name'], proxy_nodes[res['.name']]); + }); + so.depends('node', 'urltest'); + so.depends('node', 'selector'); + so.rmempty = true; + so.modalonly = true; + + so = ss.option(hp.CBIMultiValue, 'policy_nodes', _('Policy nodes'), + _('List of policy groups.')); + so.load = function(section_id) { + delete this.keylist; + delete this.vallist; + + uci.sections(data[0], 'routing_node', (res) => { + if (res['.name'] !== section_id && res.enabled === '1' && ['urltest', 'selector'].includes(res.node)) + this.value(res['.name'], res.label || res['.name']); + }); + + return this.super('load', section_id); + } + so.validate = function(section_id, value) { + if (normalizeFormList(value).includes(section_id)) + return _('Recursive outbound detected!'); return true; } + so.depends('node', 'selector'); + so.rmempty = true; + so.modalonly = true; + + so = ss.option(form.ListValue, 'selector_default', _('Default node')); + so.load = function(section_id) { + delete this.keylist; + delete this.vallist; + + let groups = normalizeFormList(this.section.formvalue(section_id, 'subscription_groups')), + subscription_nodes = normalizeFormList(this.section.formvalue(section_id, 'subscription_nodes')), + selected = normalizeFormList(this.section.formvalue(section_id, 'selected_nodes')), + policy_nodes = normalizeFormList(this.section.formvalue(section_id, 'policy_nodes')); + + this.value('', _('Default')); + for (let node of collectSelectedRoutingNodes(groups, subscription_nodes, selected, policy_nodes, section_id)) + this.value(node, proxy_nodes[node] || routing_groups[node]); + + return this.super('load', section_id); + } + so.validate = function(section_id, value) { + if (!value) + return true; + + let groups = normalizeFormList(this.section.formvalue(section_id, 'subscription_groups')), + subscription_nodes = normalizeFormList(this.section.formvalue(section_id, 'subscription_nodes')), + selected = normalizeFormList(this.section.formvalue(section_id, 'selected_nodes')), + policy_nodes = normalizeFormList(this.section.formvalue(section_id, 'policy_nodes')); + + if (!collectSelectedRoutingNodes(groups, subscription_nodes, selected, policy_nodes, section_id).includes(value)) + return _('Invalid node'); + + return true; + } + so.depends('node', 'selector'); + so.modalonly = true; + + so = ss.option(form.Flag, 'selector_interrupt_exist_connections', _('Interrupt existing connections'), + _('Interrupt existing connections when the selected outbound has changed.')); + so.depends('node', 'selector'); so.modalonly = true; so = ss.option(form.Value, 'urltest_url', _('Test URL'), @@ -593,13 +1144,7 @@ return view.extend({ so.editable = true; so = ss.taboption('field_other', form.ListValue, 'mode', _('Mode'), - _('The default rule uses the following matching logic:
' + - '(domain || domain_suffix || domain_keyword || domain_regex || ip_cidr || ip_is_private) &&
' + - '(port || port_range) &&
' + - '(source_ip_cidr || source_ip_is_private) &&
' + - '(source_port || source_port_range) &&
' + - 'other fields.
' + - 'Additionally, included rule sets can be considered merged rather than as a single rule sub-item.')); + _('In default mode, rule fields are matched by category. Any condition in the same category can match, while different categories must match at the same time. Rule sets are merged into the rule for matching and are not treated as separate sub-rules.')); so.value('default', _('Default')); so.default = 'default'; so.rmempty = false; @@ -651,7 +1196,7 @@ return view.extend({ uci.sections(data[0], 'ruleset', (res) => { if (res.enabled === '1') - this.value(res['.name'], res.label); + this.value(res['.name'], res.tag || res.label); }); return this.super('load', section_id); @@ -784,8 +1329,8 @@ return view.extend({ so.depends('action', 'resolve'); so.modalonly = true; - so = ss.taboption('field_other', form.Flag, 'resolve_disable_cache', _('Disable DNS cache'), - _('Disable DNS cache in this query.')); + so = ss.taboption('field_other', form.Flag, 'resolve_disable_cache', _('Disable DNS memory cache'), + _('Disable DNS memory cache in this query.')); so.depends('action', 'resolve'); so.modalonly = true; @@ -879,7 +1424,8 @@ return view.extend({ for (let i in hp.dns_strategy) so.value(i, hp.dns_strategy[i]); - so = ss.option(form.ListValue, 'default_server', _('Default DNS server')); + so = ss.option(form.ListValue, 'default_server', _('Default DNS server'), + _('Daily domain name resolution for clients. Choose an overseas DNS if DNS leak protection is required.')); so.load = function(section_id) { delete this.keylist; delete this.vallist; @@ -896,13 +1442,13 @@ return view.extend({ so.default = 'default-dns'; so.rmempty = false; - so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache')); + so = ss.option(form.Flag, 'disable_cache', _('Disable DNS memory cache')); - so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire')); + so = ss.option(form.Flag, 'disable_cache_expire', _('Disable DNS memory cache expiration')); so.depends('disable_cache', '0'); - so = ss.option(form.Flag, 'independent_cache', _('Independent cache per server'), - _('Make each DNS server\'s cache independent for special purposes. If enabled, will slightly degrade performance.')); + so = ss.option(form.Flag, 'independent_cache', _('Independent DNS memory cache per server'), + _('Make each DNS server\'s memory cache independent for special purposes. If enabled, will slightly degrade performance.')); so.depends('disable_cache', '0'); so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'), @@ -910,14 +1456,6 @@ return view.extend({ 'If value is an IP address instead of prefix, /32 or /128 will be appended automatically.')); so.datatype = 'or(cidr, ipaddr)'; - so = ss.option(form.Flag, 'cache_file_store_rdrc', _('Store RDRC'), - _('Store rejected DNS response cache.
' + - 'The check results of Address filter DNS rule items will be cached until expiration.')); - - so = ss.option(form.Value, 'cache_file_rdrc_timeout', _('RDRC timeout'), - _('Timeout of rejected DNS response cache in seconds. 604800 (7d) is used by default.')); - so.datatype = 'uinteger'; - so.depends('cache_file_store_rdrc', '1'); /* DNS settings end */ /* DNS servers start */ @@ -955,21 +1493,72 @@ return view.extend({ so.rmempty = false; so = ss.option(form.Value, 'server', _('Address'), - _('The address of the dns server.')); - so.datatype = 'or(hostname, ipaddr)'; + _('Full URL, e.g. https://dns.alidns.com/dns-query')); + so.placeholder = ''; + so.validate = function(section_id, value) { + let type = this.section.formvalue(section_id, 'type') || + uci.get(data[0], section_id, 'type') || + 'udp'; + + if (!validateDnsServerAddress(type, value)) + return _('Expecting: %s').format(_('valid DNS server address')); + + return true; + } + so.renderWidget = function(section_id, option_index, cfgvalue) { + let node = form.Value.prototype.renderWidget.apply(this, arguments), + input = node.querySelector('input'), + update = () => { + let type = this.section.formvalue(section_id, 'type') || + uci.get(data[0], section_id, 'type') || + 'udp'; + if (input) + input.placeholder = getDnsServerAddressPlaceholder(type); + }; + + update(); + requestAnimationFrame(() => { + let type_input = document.getElementById(this.cbid(section_id).replace(/\.server$/, '.type')); + if (type_input) { + type_input.addEventListener('change', update); + update(); + } + }); + + return node; + } so.rmempty = false; so = ss.option(form.Value, 'server_port', _('Port'), - _('The port of the DNS server.')); - so.placeholder = 'auto'; + _('Leave empty to use the default port.')); + so.placeholder = '53'; so.datatype = 'port'; + so.renderWidget = function(section_id, option_index, cfgvalue) { + let node = form.Value.prototype.renderWidget.apply(this, arguments), + input = node.querySelector('input'), + update = () => { + let type = this.section.formvalue(section_id, 'type') || + uci.get(data[0], section_id, 'type') || + 'udp'; + if (input) + input.placeholder = getDnsServerDefaultPort(type); + }; - so = ss.option(form.Value, 'path', _('Path'), - _('The path of the DNS server.')); - so.placeholder = '/dns-query'; - so.depends('type', 'https'); - so.depends('type', 'h3'); - so.modalonly = true; + update(); + requestAnimationFrame(() => { + let type_input = document.getElementById(this.cbid(section_id).replace(/\.server_port$/, '.type')); + if (type_input) { + type_input.addEventListener('change', update); + update(); + } + }); + + return node; + } + so.depends('type', 'udp'); + so.depends('type', 'tcp'); + so.depends('type', 'tls'); + so.depends('type', 'quic'); so = ss.option(form.DynamicList, 'headers', _('Headers'), _('Additional headers to be sent to the DNS server.')); @@ -986,7 +1575,7 @@ return view.extend({ so.modalonly = true; so = ss.option(form.ListValue, 'address_resolver', _('Address resolver'), - _('Tag of a another server to resolve the domain name in the address. Required if address contains domain.')); + _('Used to resolve DNS server addresses in domain form. IP addresses do not require it.')); so.load = function(section_id) { delete this.keylist; delete this.vallist; @@ -1073,13 +1662,7 @@ return view.extend({ so.editable = true; so = ss.taboption('field_other', form.ListValue, 'mode', _('Mode'), - _('The default rule uses the following matching logic:
' + - '(domain || domain_suffix || domain_keyword || domain_regex) &&
' + - '(port || port_range) &&
' + - '(source_ip_cidr || source_ip_is_private) &&
' + - '(source_port || source_port_range) &&
' + - 'other fields.
' + - 'Additionally, included rule sets can be considered merged rather than as a single rule sub-item.')); + _('In default mode, rule fields are matched by category. Any condition in the same category can match, while different categories must match at the same time. Rule sets are merged into the rule for matching and are not treated as separate sub-rules.')); so.value('default', _('Default')); so.default = 'default'; so.rmempty = false; @@ -1124,7 +1707,7 @@ return view.extend({ uci.sections(data[0], 'ruleset', (res) => { if (res.enabled === '1') - this.value(res['.name'], res.label); + this.value(res['.name'], res.tag || res.label); }); return this.super('load', section_id); @@ -1178,8 +1761,8 @@ return view.extend({ so.depends('action', 'route'); so.modalonly = true; - so = ss.taboption('field_other', form.Flag, 'dns_disable_cache', _('Disable dns cache'), - _('Disable cache and save cache in this query.')); + so = ss.taboption('field_other', form.Flag, 'dns_disable_cache', _('Disable DNS memory cache'), + _('Disable DNS memory cache and persistent cache in this query.')); so.depends('action', 'route'); so.depends('action', 'route-options'); so.modalonly = true; @@ -1319,13 +1902,37 @@ return view.extend({ ss.nodescriptions = true; ss.modaltitle = L.bind(hp.loadModalTitle, this, _('Rule set'), _('Add a rule set'), data[0]); ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + ss.renderSectionAdd = L.bind(renderRuleSetAdd, this, ss); so = ss.option(form.Value, 'label', _('Label')); so.load = L.bind(hp.loadDefaultLabel, this, data[0]); so.validate = L.bind(hp.validateUniqueValue, this, data[0], 'ruleset', 'label'); so.modalonly = true; + so = ss.option(form.Value, 'tag', _('Tag'), + _('Used by sing-box rule sets. Please fill it in yourself.')); + so.rmempty = false; + so.validate = function(section_id, value) { + if (section_id) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + if (!value.match(/^[A-Za-z0-9_.-]+$/)) + return _('Expecting: %s').format(_('valid tag')); + + let duplicate = false; + uci.sections(data[0], 'ruleset', (res) => { + if (res['.name'] !== section_id) + if ((res.tag || ('cfg-' + res['.name'] + '-rule')) === value) + duplicate = true; + }); + if (duplicate) + return _('Expecting: %s').format(_('unique value')); + } + + return true; + } + so.modalonly = true; + so = ss.option(form.Flag, 'enabled', _('Enable')); so.default = so.enabled; so.rmempty = false; @@ -1343,9 +1950,9 @@ return view.extend({ so.default = 'binary'; so.rmempty = false; - so = ss.option(form.Value, 'path', _('Path')); + so = ss.option(form.Value, 'path', _('Path'), + _('The default rule set directory is /etc/homeproxy/ruleset/.')); so.datatype = 'file'; - so.placeholder = '/etc/homeproxy/ruleset/example.json'; so.rmempty = false; so.depends('type', 'local'); so.modalonly = true; @@ -1389,15 +1996,129 @@ return view.extend({ } so.depends('type', 'remote'); - so = ss.option(form.Value, 'update_interval', _('Update interval'), - _('Update interval of rule set.')); - so.placeholder = '1d'; + so = ss.option(form.Value, 'remote_path', _('Path')); + so.default = '/etc/homeproxy/ruleset/'; + so.placeholder = '/etc/homeproxy/ruleset/'; + so.validate = function(section_id, value) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + if (!value.match(/^\//)) + return _('Expecting: %s').format(_('absolute path')); + + return true; + } + so.rmempty = false; + so.depends('type', 'remote'); + so.modalonly = true; + + so = ss.option(form.Flag, 'auto_update', _('Auto update')); + so.default = '1'; + so.rmempty = false; + so.depends('type', 'remote'); + so.modalonly = true; + + so = ss.option(form.Value, 'update_interval', _('Update time (weekly)')); + so.renderWidget = function() { + return hp.renderCronSelector.apply(this, arguments); + }; + so.default = '0 0 * * *'; + so.rmempty = false; + so.validate = function(section_id, value) { + value = value || this.formvalue(section_id); + if (!validateUpdateCron(value)) + return _('Expecting: %s').format(_('valid cron expression')); + + return true; + }; so.depends('type', 'remote'); /* Rule set settings end */ - /* ACL settings start */ s.tab('control', _('Access Control')); + /* NTP settings start */ + s.tab('ntp', _('NTP Settings')); + o = s.taboption('ntp', form.SectionValue, '_ntp', form.NamedSection, 'ntp', 'homeproxy'); + o.depends('routing_mode', 'custom'); + ss = o.subsection; + + so = ss.option(form.Flag, 'enabled', _('Enable NTP')); + so.default = so.enabled; + so.rmempty = false; + + so = ss.option(form.Value, 'server', _('NTP server address')); + so.default = 'ntp.aliyun.com'; + so.placeholder = 'ntp.aliyun.com'; + so.rmempty = false; + so.validate = function(_section_id, value) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + + return stubValidator.apply('hostname', value) || + stubValidator.apply('ip4addr', value) || + stubValidator.apply('ip6addr', value); + }; + so.depends('enabled', '1'); + + so = ss.option(form.Value, 'server_port', _('NTP server port')); + so.default = '123'; + so.placeholder = '123'; + so.datatype = 'port'; + so.rmempty = false; + so.validate = function(_section_id, value) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + + return stubValidator.apply('port', value); + }; + so.depends('enabled', '1'); + + so = ss.option(form.Value, 'interval', _('NTP time synchronization interval'), + _('NTP service always uses direct connection and mainly provides accurate time for sing-box features that depend on it, such as TLS certificate verification, VMess, Reality/uTLS and other time-sensitive connection scenarios.') + '
' + + _('Time format examples: 1m = 1 minute, 1h = 1 hour, 1d = 1 day.')); + so.default = '30m'; + so.placeholder = '30m'; + so.rmempty = false; + so.validate = function(_section_id, value) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + if (!validateDuration(value)) + return _('Expecting: %s').format(_('valid duration')); + + return true; + }; + so.depends('enabled', '1'); + /* NTP settings end */ + + /* Cache settings start */ + s.tab('cache', _('Persistent Cache Settings')); + o = s.taboption('cache', form.SectionValue, '_cache', form.NamedSection, 'cache', 'homeproxy'); + o.depends('routing_mode', 'custom'); + ss = o.subsection; + + so = ss.option(form.ListValue, 'enabled', _('Enable cache file')); + so.value('1', _('Enable')); + so.value('0', _('Disable')); + so.default = '1'; + so.rmempty = false; + + so = ss.option(form.Value, 'path', _('Persistent cache file path')); + so.placeholder = '/etc/homeproxy/cache.db'; + so.depends('enabled', '1'); + + so = ss.option(form.ListValue, 'store_rdrc', _('Persist RDRC cache')); + so.value('1', _('Enable')); + so.value('0', _('Disable')); + so.default = '1'; + so.rmempty = false; + so.depends('enabled', '1'); + + so = ss.option(form.Value, 'rdrc_timeout', _('RDRC timeout'), + _('Timeout of rejected DNS response cache in seconds. 604800 (7d) is used by default.')); + so.datatype = 'uinteger'; + so.depends({'enabled': '1', 'store_rdrc': '1'}); + /* Cache settings end */ + + /* ACL settings start */ o = s.taboption('control', form.SectionValue, '_control', form.NamedSection, 'control', 'homeproxy'); ss = o.subsection; @@ -1543,6 +2264,101 @@ return view.extend({ /* Direct domain list end */ /* ACL settings end */ + /* Panel settings start */ + s.tab('panel', _('Panel Settings')); + o = s.taboption('panel', form.SectionValue, '_clash_api', form.NamedSection, 'clash_api', 'homeproxy'); + o.depends('routing_mode', 'custom'); + ss = o.subsection; + + so = ss.option(form.Value, 'external_ui', _('UI path')); + so.placeholder = '/etc/homeproxy/run/ui'; + so.default = '/etc/homeproxy/run/ui'; + + const panelPresetUrls = { + 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip': 'Zashboard CDN Fonts (gh-proxy)', + 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip': 'Zashboard CDN Fonts', + 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip': 'Zashboard 完整版 (gh-proxy)', + 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip': 'Zashboard 完整版', + 'https://gh-proxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip': 'MetaCubeXD (gh-proxy)', + 'https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip': 'MetaCubeXD', + 'https://gh-proxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip': 'YACD (gh-proxy)', + 'https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip': 'YACD', + 'https://gh-proxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip': 'Razord (gh-proxy)', + 'https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip': 'Razord' + }; + + so = ss.option(form.ListValue, 'external_ui_download_url', _('UI download URL')); + so.value('https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip', 'Zashboard CDN Fonts (gh-proxy)'); + so.value('https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip', 'Zashboard CDN Fonts'); + so.value('https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip', 'Zashboard 完整版 (gh-proxy)'); + so.value('https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip', 'Zashboard 完整版'); + so.value('https://gh-proxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD (gh-proxy)'); + so.value('https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD'); + so.value('https://gh-proxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD (gh-proxy)'); + so.value('https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD'); + so.value('https://gh-proxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord (gh-proxy)'); + so.value('https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord'); + so.value('https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD'); + so.value('https://gh-proxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD (gh-proxy)'); + so.value('https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD'); + so.value('https://gh-proxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord (gh-proxy)'); + so.value('https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord'); + so.value('__custom__', _('Custom')); + so.default = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip'; + so.load = function(section_id) { + const value = uci.get(data[0], section_id, 'external_ui_download_url') ?? this.default; + return (value in panelPresetUrls) ? value : '__custom__'; + }; + so.write = function(section_id, value) { + if (value === '__custom__') { + const customValue = (this.section?.formvalue(section_id, 'external_ui_download_url_custom') || '').trim(); + uci.set(data[0], section_id, 'external_ui_download_url', customValue); + } else { + uci.set(data[0], section_id, 'external_ui_download_url', value); + } + }; + + so = ss.option(form.Value, 'external_ui_download_url_custom', _('Custom URL')); + so.placeholder = 'https://example.com/dist.zip'; + so.depends('external_ui_download_url', '__custom__'); + so.load = function(section_id) { + const value = (uci.get(data[0], section_id, 'external_ui_download_url') || '').trim(); + return (value in panelPresetUrls) ? '' : value; + }; + so.write = function(section_id, value) { + uci.set(data[0], section_id, 'external_ui_download_url_custom', (value || '').trim()); + }; + so.remove = function(section_id) { + uci.unset(data[0], section_id, 'external_ui_download_url_custom'); + }; + + so = ss.option(form.ListValue, 'external_ui_download_detour', _('UI download detour')); + so.value('', _('Default')); + so.value('direct-out', _('Direct')); + uci.sections(data[0], 'routing_node', (res) => { + if (res.enabled === '1') + so.value(res['.name'], res.label); + }); + so.default = 'direct-out'; + + so = ss.option(form.Value, 'external_controller', _('API listen'), + _('Use 0.0.0.0:port to open the panel from your browser.')); + so.placeholder = '0.0.0.0:9095'; + so.default = '0.0.0.0:9095'; + so.datatype = 'ipaddrport(1)'; + so.rmempty = false; + + so = ss.option(form.Value, 'secret', _('API password')); + so.password = true; + + so = ss.option(form.ListValue, 'default_mode', _('Default mode')); + so.value('rule', _('Rule')); + so.value('global', _('Global')); + so.value('direct', _('Direct')); + so.default = 'rule'; + so.rmempty = false; + /* Panel settings end */ + return m.render(); } }); diff --git a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js index 342a4fb7b..5b5b19238 100644 --- a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js +++ b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js @@ -7,6 +7,7 @@ 'use strict'; 'require form'; 'require fs'; +'require rpc'; 'require uci'; 'require ui'; 'require view'; @@ -14,11 +15,207 @@ 'require homeproxy as hp'; 'require tools.widgets as widgets'; +const callNodeLatencyTest = rpc.declare({ + object: 'luci.homeproxy', + method: 'node_latency_test', + params: ['node', 'mode'], + expect: { '': {} } +}); + +const callRCInit = rpc.declare({ + object: 'rc', + method: 'init', + params: ['name', 'action'], + expect: { '': {} } +}); + +function normalizeList(value) { + return Array.isArray(value) ? value : (value ? [value] : []); +} + +function getSubscriptionInfo(config) { + const urls = normalizeList(uci.get(config, 'subscription', 'subscription_url')), + names = normalizeList(uci.get(config, 'subscription', 'subscription_name')), + info = []; + + for (let i = 0; i < urls.length; i++) { + const url = urls[i], + cleanUrl = String(url || '').replace(/#.*$/, ''), + title = String(names[i] || '').trim(); + + if (!url) + continue; + + info.push({ + hash: hp.calcStringMD5(cleanUrl), + title, + url + }); + } + + return info; +} + +const SubscriptionList = form.Value.extend({ + __name__: 'CBI.HomeProxySubscriptionList', + + cfgvalue(section_id) { + return { + urls: normalizeList(uci.get('homeproxy', section_id, 'subscription_url')), + names: normalizeList(uci.get('homeproxy', section_id, 'subscription_name')) + }; + }, + + renderWidget(section_id, _option_index, cfgvalue) { + const value = cfgvalue || { urls: [], names: [] }, + rows = E('div', { + id: this.cbid(section_id), + 'class': 'homeproxy-subscription-list', + 'style': 'display: flex; flex-direction: column; gap: .45em; width: min(100%, 42em);' + }); + + rows.appendChild(E('div', { + 'class': 'homeproxy-subscription-header', + 'style': 'display: grid; grid-template-columns: minmax(8em, 13em) minmax(16em, 1fr) auto; gap: .35em; color: var(--text-muted, #6b7280); font-size: 90%;' + }, [ + E('span', {}, [ _('\u8ba2\u9605\u540d\u79f0') ]), + E('span', {}, [ _('\u8ba2\u9605\u5730\u5740') ]), + E('span') + ])); + + const addRow = (name, url) => { + const row = E('div', { + 'class': 'homeproxy-subscription-row', + 'style': 'display: grid; grid-template-columns: minmax(8em, 13em) minmax(16em, 1fr) auto; gap: .35em; align-items: stretch;' + }, [ + E('input', { + 'type': 'text', + 'class': 'cbi-input-text subscription-name', + 'placeholder': _('\u8ba2\u9605\u540d\u79f0'), + 'value': name || '', + 'style': 'width: 100%; box-sizing: border-box;' + }), + E('input', { + 'type': 'text', + 'class': 'cbi-input-text subscription-url', + 'placeholder': _('\u8ba2\u9605\u5730\u5740'), + 'value': url || '', + 'style': 'width: 100%; box-sizing: border-box;' + }), + E('button', { + 'type': 'button', + 'class': 'cbi-button cbi-button-remove', + 'click': () => { + row.remove(); + if (!rows.querySelector('.homeproxy-subscription-row')) + addRow('', ''); + } + }, [ '-' ]) + ]); + + if (rows._addButton) + rows.insertBefore(row, rows._addButton); + else + rows.appendChild(row); + }; + + for (let i = 0; i < Math.max(value.urls.length, 1); i++) + addRow(value.names[i], value.urls[i]); + + rows._addButton = E('button', { + 'type': 'button', + 'class': 'cbi-button cbi-button-add', + 'style': 'width: 3em;', + 'click': () => addRow('', '') + }, [ '+' ]); + rows.appendChild(rows._addButton); + + return rows; + }, + + formvalue(section_id) { + const node = document.getElementById(this.cbid(section_id)), + names = [], + urls = []; + + node?.querySelectorAll('.homeproxy-subscription-row').forEach((row) => { + const name = row.querySelector('.subscription-name')?.value.trim() || '', + url = row.querySelector('.subscription-url')?.value.trim() || ''; + + if (!name && !url) + return; + + names.push(name); + urls.push(url); + }); + + return { names, urls }; + }, + + validate(_section_id, value) { + const names = value?.names || [], + urls = value?.urls || [], + usedNames = {}, + usedUrls = {}; + + if (!names.length || !urls.length) + return _('\u8ba2\u9605\u540d\u79f0\u548c\u8ba2\u9605\u5730\u5740\u4e3a\u5fc5\u586b\u9879'); + + for (let i = 0; i < Math.max(names.length, urls.length); i++) { + const name = String(names[i] || '').trim(), + url = String(urls[i] || '').trim(), + normalizedUrl = url.replace(/#.*$/, ''); + + if (!name || !url) + return _('\u8ba2\u9605\u540d\u79f0\u548c\u8ba2\u9605\u5730\u5740\u5fc5\u987b\u6210\u5bf9\u586b\u5199'); + + if (usedNames[name]) + return _('\u8ba2\u9605\u540d\u79f0\u4e0d\u80fd\u91cd\u590d'); + usedNames[name] = true; + + if (usedUrls[normalizedUrl]) + return _('\u8ba2\u9605\u5730\u5740\u4e0d\u80fd\u91cd\u590d'); + usedUrls[normalizedUrl] = true; + + try { + let parsed = new URL(url); + if (!parsed.hostname) + return _('Expecting: %s').format(_('valid URL')); + } catch(e) { + return _('Expecting: %s').format(_('valid URL')); + } + } + + return true; + }, + + write(section_id, value) { + if (value?.urls?.length) { + uci.set('homeproxy', section_id, 'subscription_url', value.urls); + uci.set('homeproxy', section_id, 'subscription_name', value.names); + } else { + uci.unset('homeproxy', section_id, 'subscription_url'); + uci.unset('homeproxy', section_id, 'subscription_name'); + } + } +}); + function allowInsecureConfirm(ev, _section_id, value) { if (value === '1' && !confirm(_('Are you sure to allow insecure?'))) ev.target.firstElementChild.checked = null; } +function normalizeHysteriaHoppingPort(mport) { + if (!mport) + return null; + + const ports = mport.split(',') + .map((p) => p.trim()) + .filter((p) => p.length) + .map((p) => (/^\d+$/.test(p) ? `${p}:${p}` : p.replace('-', ':'))); + return ports.length ? ports : null; +} + function parseShareLink(uri, features) { let config, url, params; @@ -29,6 +226,11 @@ function parseShareLink(uri, features) { /* https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md */ url = new URL('http://' + uri[1]); params = url.searchParams; + let anytls_fp = params.get('fp'); + if (['none', 'disable', 'disabled'].includes(anytls_fp)) + anytls_fp = null; + else if (!anytls_fp) + anytls_fp = 'chrome'; /* Check if password exists */ if (!url.username) @@ -42,7 +244,8 @@ function parseShareLink(uri, features) { password: url.username ? decodeURIComponent(url.username) : null, tls: '1', tls_sni: params.get('sni'), - tls_insecure: (params.get('insecure') === '1') ? '1' : '0' + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', + tls_utls: features.with_utls ? anytls_fp : null }; break; @@ -75,6 +278,7 @@ function parseShareLink(uri, features) { type: 'hysteria', address: url.hostname, port: url.port || '80', + hysteria_hopping_port: normalizeHysteriaHoppingPort(params.get('mport')), hysteria_protocol: params.get('protocol') || 'udp', hysteria_auth_type: params.get('auth') ? 'string' : null, hysteria_auth_payload: params.get('auth'), @@ -82,9 +286,9 @@ function parseShareLink(uri, features) { hysteria_down_mbps: params.get('downmbps'), hysteria_up_mbps: params.get('upmbps'), tls: '1', + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', tls_sni: params.get('peer'), - tls_alpn: params.get('alpn'), - tls_insecure: (params.get('insecure') === '1') ? '1' : '0' + tls_alpn: params.get('alpn') }; break; @@ -105,11 +309,12 @@ function parseShareLink(uri, features) { password: url.username ? ( decodeURIComponent(url.username + (url.password ? (':' + url.password) : '')) ) : null, + hysteria_hopping_port: normalizeHysteriaHoppingPort(params.get('mport')), hysteria_obfs_type: params.get('obfs'), hysteria_obfs_password: params.get('obfs-password'), tls: '1', - tls_sni: params.get('sni'), - tls_insecure: params.get('insecure') ? '1' : '0' + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', + tls_sni: params.get('sni') }; break; @@ -212,6 +417,7 @@ function parseShareLink(uri, features) { password: decodeURIComponent(url.username), transport: params.get('type') !== 'tcp' ? params.get('type') : null, tls: '1', + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', tls_sni: params.get('sni') }; switch (params.get('type')) { @@ -249,6 +455,7 @@ function parseShareLink(uri, features) { tuic_congestion_control: params.get('congestion_control'), tuic_udp_relay_mode: params.get('udp_relay_mode'), tls: '1', + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', tls_sni: params.get('sni'), tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null }; @@ -276,6 +483,7 @@ function parseShareLink(uri, features) { uuid: url.username, transport: params.get('type') !== 'tcp' ? params.get('type') : null, tls: ['tls', 'xtls', 'reality'].includes(params.get('security')) ? '1' : '0', + tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0', tls_sni: params.get('sni'), tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null, tls_reality: (params.get('security') === 'reality') ? '1' : '0', @@ -388,13 +596,378 @@ function parseShareLink(uri, features) { return config; } -function renderNodeSettings(section, data, features, main_node, routing_mode) { +const NODE_LATENCY_ROW_STATES = Object.freeze({ + UNTESTED: 'untested', + TESTING: 'testing', + SUCCESS: 'success', + TIMEOUT: 'timeout', + ERROR: 'error' +}); + +const NODE_LATENCY_ROW_STATE_VALUES = Object.freeze(Object.values(NODE_LATENCY_ROW_STATES)); +const NODE_LATENCY_TEST_MODES = Object.freeze([ 'icmp', 'rp' ]); + +function remapLegacyNodeLatencyTestMode(mode) { + return mode === 'tcp' ? 'icmp' : mode; +} + +function normalizeNodeLatencyTestMode(mode) { + mode = remapLegacyNodeLatencyTestMode(mode); + return NODE_LATENCY_TEST_MODES.includes(mode) ? mode : 'icmp'; +} + +function resolveNodeLatencyResultByMode(result, mode) { + let active_mode = normalizeNodeLatencyTestMode(mode || result?.mode); + let active_latency_ms = (result && result.latency_ms != null) ? result.latency_ms : null; + let active_error = (result && result.error) ? result.error : null; + let active_state = result?.state; + + if (active_mode === 'icmp') { + if (result && result.icmp_latency_ms != null) + active_latency_ms = result.icmp_latency_ms; + if (result && result.icmp_error) + active_error = result.icmp_error; + } + + if (active_error?.code?.endsWith('_timeout')) + active_state = NODE_LATENCY_ROW_STATES.TIMEOUT; + else if (active_error) + active_state = NODE_LATENCY_ROW_STATES.ERROR; + else if (active_latency_ms != null) + active_state = NODE_LATENCY_ROW_STATES.SUCCESS; + + return { + mode: active_mode, + state: active_state, + latency_ms: active_latency_ms, + error: active_error + }; +} + +function getNodeLatencyErrorText(error_code) { + switch (error_code) { + case 'backend_execution failed': + return _('Backend execution failed'); + case 'icmp_failed': + return _('ICMP Failed'); + case 'icmp_probe_unavailable': + return _('ICMP Probe unavailable'); + case 'icmp_timeout': + return _('ICMP Timeout'); + case 'invalid_node': + return _('Invalid node'); + case 'invalid_response': + return _('Invalid response'); + case 'probe_failed': + return _('Probe failed'); + case 'rpc_failed': + return _('RPC failed'); + default: + return _('Error'); + } +} + +function getNodeLatencyStatusText(row_state) { + if (!row_state) + return _('Not Tested'); + + switch (row_state.state) { + case NODE_LATENCY_ROW_STATES.TESTING: + return _('Testing'); + case NODE_LATENCY_ROW_STATES.SUCCESS: + return (row_state.latency_ms != null) ? _('%s ms').format(row_state.latency_ms) : _('Success'); + case NODE_LATENCY_ROW_STATES.TIMEOUT: + return _('Timeout'); + case NODE_LATENCY_ROW_STATES.ERROR: + return getNodeLatencyErrorText(row_state.error?.code); + case NODE_LATENCY_ROW_STATES.UNTESTED: + default: + return _('Not Tested'); + } +} + +function getNodeLatencyStatusStyle(row_state) { + if (!row_state) + return 'color:gray'; + + switch (row_state.state) { + case NODE_LATENCY_ROW_STATES.TESTING: + return 'color:#0a84ff'; + case NODE_LATENCY_ROW_STATES.SUCCESS: + return 'color:green'; + case NODE_LATENCY_ROW_STATES.TIMEOUT: + return 'color:#ff8c00'; + case NODE_LATENCY_ROW_STATES.ERROR: + return 'color:red'; + case NODE_LATENCY_ROW_STATES.UNTESTED: + default: + return 'color:gray'; + } +} + +function renderNodeLatencyStatus(row_state) { + return '%s'.format(getNodeLatencyStatusStyle(row_state), getNodeLatencyStatusText(row_state)); +} + +function renderNodeLatencyStatusNode(row_state, attrs) { + return E('strong', Object.assign({ + 'style': getNodeLatencyStatusStyle(row_state) + }, attrs || {}), [ getNodeLatencyStatusText(row_state) ]); +} + +function getNodeLatencyActionTitle(row_state) { + return (row_state?.state === NODE_LATENCY_ROW_STATES.TESTING) ? _('Testing') : _('Test'); +} + +function parseNodeLatencySectionId(widget_id) { + let matched = String(widget_id || '').match(/^cbi-homeproxy-(.*)-_test_latency$/); + return matched ? matched[1] : null; +} + +function parseActiveSubscriptionTabId(value) { + let matched = String(value || '').match(/(?:^|[^\w])(sub_[0-9a-f]+)(?:$|[^\w])/i); + return matched ? matched[1] : null; +} + +function parseActiveNodeListTabId(value) { + let matched = String(value || '').match(/(?:^|[^\w])(node|subscription|sub_[0-9a-f]+)(?:$|[^\w])/i); + return matched ? matched[1].toLowerCase() : null; +} + +function getActiveNodeListTabId(root_el) { + let active_tab = null; + let active_tab_nodes = root_el.querySelectorAll('.cbi-tabmenu li.cbi-tab-active, .cbi-tabmenu li.active, .cbi-tabmenu [aria-selected="true"]'); + + for (let node of active_tab_nodes) { + let tab_id = parseActiveNodeListTabId(node?.getAttribute('data-tab')) + || parseActiveNodeListTabId(node?.dataset?.tab) + || parseActiveNodeListTabId(node?.getAttribute('id')); + if (!tab_id && node?.querySelector) + tab_id = parseActiveNodeListTabId(node.querySelector('a')?.getAttribute('href')) + || parseActiveNodeListTabId(node.querySelector('a')?.getAttribute('data-tab')); + + if (tab_id) { + active_tab = tab_id; + break; + } + } + + if (!active_tab) { + let fallback_tab_nodes = root_el.querySelectorAll('.cbi-tabmenu li.cbi-tab[data-tab], .cbi-tabmenu li[data-tab]:not(.cbi-tab-disabled)'); + + for (let node of fallback_tab_nodes) { + let tab_id = parseActiveNodeListTabId(node?.getAttribute('data-tab')) || parseActiveNodeListTabId(node?.dataset?.tab); + if (tab_id) { + active_tab = tab_id; + break; + } + } + } + + return active_tab; +} + +function shouldShowBulkLatencyButton(root_el) { + let active_tab = getActiveNodeListTabId(root_el); + + if (!active_tab) + return false; + + return !!parseActiveSubscriptionTabId(active_tab); +} + +function getActiveSubscriptionTabId(root_el) { + let active_tab = getActiveNodeListTabId(root_el); + return parseActiveSubscriptionTabId(active_tab); +} + +function getActiveSubscriptionTabContainer(root_el, active_tab) { + if (!active_tab) + return null; + + let candidates = []; + for (let selector of [ + '.cbi-tabcontainer[data-tab="%s"]'.format(active_tab), + '[data-tab="%s"]'.format(active_tab), + '[id="tab.%s"]'.format(active_tab), + '[id="tab-%s"]'.format(active_tab), + '[id="%s"]'.format(active_tab) + ]) { + candidates = candidates.concat(Array.from(root_el.querySelectorAll(selector))); + } + + return candidates.find((node) => !node.closest('.cbi-tabmenu') && node.querySelector('div[id$="-_test_latency"]')) || null; +} + +function getActiveSubscriptionLatencySectionIds(root_el) { + let active_tab = getActiveSubscriptionTabId(root_el); + let active_container = getActiveSubscriptionTabContainer(root_el, active_tab); + if (!active_container) + return []; + + let widgets = Array.from(active_container.querySelectorAll('div[id$="-_test_latency"]')); + return widgets.map((node) => parseNodeLatencySectionId(node.id)).filter((sid) => !!sid); +} + +function createNodeLatencyRowStateModel() { + let rows = Object.create(null); + + const buildRowState = (state, result, mode) => { + let resolved_result = resolveNodeLatencyResultByMode(result, mode); + + return { + state: state, + mode: resolved_result.mode, + latency_ms: resolved_result.latency_ms, + icmp_latency_ms: (result && result.icmp_latency_ms != null) ? result.icmp_latency_ms : null, + measured_at: (result && result.measured_at) ? result.measured_at : null, + error: resolved_result.error, + icmp_error: (result && result.icmp_error) ? result.icmp_error : null + }; + }; + + const normalizeState = (state) => { + if (NODE_LATENCY_ROW_STATE_VALUES.includes(state)) + return state; + + return NODE_LATENCY_ROW_STATES.ERROR; + }; + + const ensure = (section_id) => { + if (!section_id) + return buildRowState(NODE_LATENCY_ROW_STATES.UNTESTED, null, 'icmp'); + + if (!rows[section_id]) + rows[section_id] = buildRowState(NODE_LATENCY_ROW_STATES.UNTESTED, null, 'icmp'); + + return rows[section_id]; + }; + + return { + states: NODE_LATENCY_ROW_STATES, + + get: (section_id) => ensure(section_id), + + set: (section_id, state, result, mode) => { + if (!section_id) + return null; + + rows[section_id] = buildRowState(normalizeState(state), result, mode); + return rows[section_id]; + }, + + setFromProbeResult: (section_id, result, mode) => { + if (!section_id) + return null; + + let resolved_result = resolveNodeLatencyResultByMode(result, mode); + let state = resolved_result.state ? normalizeState(resolved_result.state) : NODE_LATENCY_ROW_STATES.ERROR; + rows[section_id] = buildRowState(state, result, mode); + return rows[section_id]; + }, + + clear: (section_id) => { + if (!section_id) + return; + + delete rows[section_id]; + }, + + reset: () => { + rows = Object.create(null); + }, + + snapshot: () => rows + }; +} + +function renderNodeSettings(section, data, features, main_node, routing_mode, node_latency_row_state) { let s = section, o; + if (typeof globalThis !== 'undefined') { + globalThis.__hpNodeLatencySections = globalThis.__hpNodeLatencySections || {}; + globalThis.__hpNodeLatencyTrigger = function(section_id) { + let target = globalThis.__hpNodeLatencySections?.[section_id]; + return target ? target.handleNodeLatencyTest(section_id) : false; + }; + } s.rowcolors = true; s.sortable = true; s.nodescriptions = true; + s.max_cols = 7; s.modaltitle = L.bind(hp.loadModalTitle, this, _('Node'), _('Add a node'), data[0]); s.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); + s.node_latency_row_state = node_latency_row_state; + s.getNodeLatencyRowState = function(section_id) { + return node_latency_row_state.get(section_id); + } + s.getNodeLatencyTestMode = function() { + let mode_option = this.map.lookupOption('latency_test_mode', 'subscription')?.[0]; + let mode_value = mode_option ? mode_option.formvalue('subscription') : uci.get(data[0], 'subscription', 'latency_test_mode'); + + return normalizeNodeLatencyTestMode(mode_value); + } + s.syncNodeLatencyTestMode = function(mode) { + let normalized_mode = normalizeNodeLatencyTestMode(mode); + let saved_mode_value = uci.get(data[0], 'subscription', 'latency_test_mode'); + let saved_mode = normalizeNodeLatencyTestMode(saved_mode_value); + + if (saved_mode === normalized_mode && saved_mode_value !== 'tcp') + return Promise.resolve(normalized_mode); + + uci.set(data[0], 'subscription', 'latency_test_mode', normalized_mode); + return uci.save().then(() => normalized_mode); + } + s.setNodeLatencyRowState = function(section_id, state, result, mode) { + return node_latency_row_state.set(section_id, state, result, mode); + } + s.setNodeLatencyRowStateFromProbeResult = function(section_id, result, mode) { + return node_latency_row_state.setFromProbeResult(section_id, result, mode); + } + s.refreshNodeLatencyRow = function(section_id) { + let row_state = this.getNodeLatencyRowState(section_id); + let latency_id = 'cbi-%s-%s-_latency'.format(data[0], section_id); + let test_id = 'cbi-%s-%s-_test_latency'.format(data[0], section_id); + + let status_widget = this.map.findElement('id', latency_id); + if (status_widget) + status_widget.innerHTML = renderNodeLatencyStatus(row_state); + + let test_widget = this.map.findElement('id', test_id); + let test_button = test_widget ? test_widget.querySelector('button') : null; + if (test_button) { + test_button.textContent = getNodeLatencyActionTitle(row_state); + test_button.disabled = (row_state.state === NODE_LATENCY_ROW_STATES.TESTING) ? true : null; + } + } + + s.handleNodeLatencyTest = function(section_id) { + let mode = this.getNodeLatencyTestMode(); + this.setNodeLatencyRowState(section_id, NODE_LATENCY_ROW_STATES.TESTING, null, mode); + this.refreshNodeLatencyRow(section_id); + + return this.syncNodeLatencyTestMode(mode).then((mode) => L.resolveDefault(callNodeLatencyTest(section_id, mode), null).then((result) => { + if (result?.node === section_id) + this.setNodeLatencyRowStateFromProbeResult(section_id, result, mode); + else + this.setNodeLatencyRowState(section_id, NODE_LATENCY_ROW_STATES.ERROR, { + mode: mode, + error: { + code: 'invalid_response', + message: 'unexpected node latency response' + } + }, mode); + })).catch((err) => { + this.setNodeLatencyRowState(section_id, NODE_LATENCY_ROW_STATES.ERROR, { + mode: mode, + error: { + code: 'rpc_failed', + message: String(err) + } + }, mode); + }).then(() => { + this.refreshNodeLatencyRow(section_id); + }); + } if (routing_mode !== 'custom') { o = s.option(form.Button, '_apply', _('Apply')); @@ -419,6 +992,41 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { } } + o = s.option(form.DummyValue, '_latency', _('Latency')); + o.rawhtml = true; + o.modalonly = false; + o.textvalue = function(section_id) { + return E('span', { 'id': 'cbi-%s-%s-_latency'.format(data[0], section_id) }, [ + renderNodeLatencyStatusNode(this.section.getNodeLatencyRowState(section_id)) + ]); + } + + o = s.option(form.Button, '_test_latency', _('Test')); + o.editable = true; + o.modalonly = false; + o.inputstyle = 'action'; + o.inputtitle = function(section_id) { + return getNodeLatencyActionTitle(this.section.getNodeLatencyRowState(section_id)); + } + o.renderWidget = function(section_id, _option_index, cfgvalue) { + let hiddenEl = new ui.Hiddenfield((cfgvalue != null) ? cfgvalue : '', { + id: this.cbid(section_id) + }); + let outputEl = E('output', { 'for': this.cbid(section_id) }); + let row_state = s.getNodeLatencyRowState(section_id); + + if (typeof globalThis !== 'undefined' && globalThis.__hpNodeLatencySections) + globalThis.__hpNodeLatencySections[section_id] = s; + + outputEl.appendChild(E('button', { + 'class': 'cbi-button cbi-button-action', + 'onclick': 'return globalThis.__hpNodeLatencyTrigger(%s);'.format(JSON.stringify(section_id)), + 'disabled': (row_state.state === NODE_LATENCY_ROW_STATES.TESTING) ? true : null + }, [ getNodeLatencyActionTitle(row_state) ])); + + return E([ outputEl, hiddenEl.render() ]); + } + o = s.option(form.Value, 'label', _('Label')); o.load = L.bind(hp.loadDefaultLabel, this, data[0]); o.validate = L.bind(hp.validateUniqueValue, this, data[0], 'node', 'label'); @@ -443,6 +1051,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { o.value('wireguard', _('WireGuard')); o.value('vless', _('VLESS')); o.value('vmess', _('VMess')); + o.default = 'direct'; o.rmempty = false; o = s.option(form.Value, 'address', _('Address')); @@ -493,15 +1102,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { } o.modalonly = true; - /* Direct config */ - o = s.option(form.ListValue, 'proxy_protocol', _('Proxy protocol'), - _('Write proxy protocol in the connection header.')); - o.value('', _('Disable')); - o.value('1', _('v1')); - o.value('2', _('v2')); - o.depends('type', 'direct'); - o.modalonly = true; - /* AnyTLS config start */ o = s.option(form.Value, 'anytls_idle_session_check_interval', _('Idle session check interval'), _('Interval checking for idle sessions, in seconds.')); @@ -746,7 +1346,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { o.modalonly = true; o = s.option(form.ListValue, 'vmess_encrypt', _('Encrypt method')); - o.value('auto'); + o.value('auto', _('Default')); o.value('none'); o.value('zero'); o.value('aes-128-gcm'); @@ -1194,19 +1794,18 @@ return view.extend({ }, render(data) { - let m, s, o, ss, so; + let m, s, o, ss, so, subscription_list_option; let main_node = uci.get(data[0], 'config', 'main_node'); let routing_mode = uci.get(data[0], 'config', 'routing_mode'); let features = data[1]; + let node_latency_row_state = createNodeLatencyRowStateModel(); + + hp.installCloseButtonText(); + + this.node_latency_row_state = node_latency_row_state; /* Cache subscription information, it will be called multiple times */ - let subinfo = []; - for (let suburl of (uci.get(data[0], 'subscription', 'subscription_url') || [])) { - const url = new URL(suburl); - const urlhash = hp.calcStringMD5(suburl.replace(/#.*$/, '')); - const title = url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname; - subinfo.push({ 'hash': urlhash, 'title': title }); - } + let subinfo = getSubscriptionInfo(data[0]); m = new form.Map('homeproxy', _('Edit nodes')); @@ -1216,7 +1815,7 @@ return view.extend({ /* User nodes start */ s.tab('node', _('Nodes')); o = s.taboption('node', form.SectionValue, '_node', form.GridSection, 'node'); - ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode); + ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode, node_latency_row_state); ss.addremove = true; ss.filter = function(section_id) { for (let info of subinfo) @@ -1287,24 +1886,7 @@ return view.extend({ ]) } ss.renderSectionAdd = function(/* ... */) { - let el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments), - nameEl = el.querySelector('.cbi-section-create-name'); - - ui.addValidator(nameEl, 'uciname', true, (v) => { - let button = el.querySelector('.cbi-section-create > .cbi-button-add'); - let uciconfig = this.uciconfig || this.map.config; - - if (!v) { - button.disabled = true; - return true; - } else if (uci.get(uciconfig, v)) { - button.disabled = true; - return _('Expecting: %s').format(_('unique UCI identifier')); - } else { - button.disabled = null; - return true; - } - }, 'blur', 'keyup'); + let el = hp.renderSectionAdd(this, arguments[0]); el.appendChild(E('button', { 'class': 'cbi-button cbi-button-add', @@ -1319,9 +1901,9 @@ return view.extend({ /* Subscription nodes start */ for (const info of subinfo) { - s.tab('sub_' + info.hash, _('Sub (%s)').format(info.title)); + s.tab('sub_' + info.hash, info.title ? _('Sub (%s)').format(info.title) : _('Sub')); o = s.taboption('sub_' + info.hash, form.SectionValue, '_sub_' + info.hash, form.GridSection, 'node'); - ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode); + ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode, node_latency_row_state); ss.filter = function(section_id) { return (uci.get(data[0], section_id, 'grouphash') === info.hash); } @@ -1336,32 +1918,35 @@ return view.extend({ _('Auto update subscriptions and geodata.')); o.rmempty = false; - o = s.taboption('subscription', form.ListValue, 'auto_update_time', _('Update time')); - for (let i = 0; i < 24; i++) - o.value(i, i + ':00'); - o.default = '2'; - o.depends('auto_update', '1'); + o = s.taboption('subscription', form.Value, 'auto_update_time', _('Update time (weekly)')); + o.renderWidget = function() { + return hp.renderCronSelector.apply(this, arguments); + }; + o.default = '0 0 * * *'; + o.rmempty = false; + o.validate = function(section_id, value) { + value = value || this.formvalue(section_id); + const matched = String(value || '').trim().match(/^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+([0-7*])$/); + if (!matched || +matched[1] > 59 || +matched[2] > 23) + return _('Expecting: %s').format(_('valid cron expression')); + + return true; + }; o = s.taboption('subscription', form.Flag, 'update_via_proxy', _('Update via proxy'), _('Update subscriptions via proxy.')); o.rmempty = false; - o = s.taboption('subscription', form.DynamicList, 'subscription_url', _('Subscription URL-s'), - _('Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.')); - o.validate = function(section_id, value) { - if (section_id && value) { - try { - let url = new URL(value); - if (!url.hostname) - return _('Expecting: %s').format(_('valid URL')); - } - catch(e) { - return _('Expecting: %s').format(_('valid URL')); - } - } + o = s.taboption('subscription', form.ListValue, 'latency_test_mode', _('Latency test mode'), + _('Choose which method node latency tests use.')); + o.value('icmp', _('ICMP')); + o.value('rp', _('RP')); + o.default = 'icmp'; + o.rmempty = false; - return true; - } + o = s.taboption('subscription', SubscriptionList, 'subscription_url', _('Subscriptions'), + _('Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.')); + subscription_list_option = o; o = s.taboption('subscription', form.ListValue, 'filter_nodes', _('Filter nodes'), _('Drop/keep specific nodes from subscriptions.')); @@ -1396,8 +1981,16 @@ return view.extend({ o.inputstyle = 'apply'; o.inputtitle = _('Save current settings'); o.onclick = function() { + const validation = subscription_list_option.validate('subscription', subscription_list_option.formvalue('subscription')); + if (validation !== true) { + ui.addNotification(null, E('p', validation)); + return Promise.resolve(); + } + return this.map.save(null, true).then(() => { - ui.changes.apply(true); + return ui.changes.apply(true); + }).then(() => { + return callRCInit('homeproxy', 'reload'); }); } @@ -1460,6 +2053,68 @@ return view.extend({ } /* Subscriptions settings end */ - return m.render(); + return m.render().then((el) => { + let bulk_testing = false; + let tabmenu_observer = null; + let bulk_button = E('button', { + 'class': 'cbi-button cbi-button-action hp-bulk-latency-test', + 'click': ui.createHandlerFn(this, async () => { + if (bulk_testing || typeof globalThis === 'undefined' || !globalThis.__hpNodeLatencyTrigger) + return false; + + let section_ids = getActiveSubscriptionLatencySectionIds(el); + if (!section_ids.length) + return false; + + bulk_testing = true; + bulk_button.textContent = _('Testing Current List...'); + bulk_button.disabled = true; + + for (let sid of section_ids) + await globalThis.__hpNodeLatencyTrigger(sid); + + bulk_testing = false; + bulk_button.textContent = _('Test Current List'); + bulk_button.disabled = false; + return true; + }) + }, [ _('Test Current List') ]); + + let updateBulkButtonVisibility = () => { + bulk_button.style.display = shouldShowBulkLatencyButton(el) ? '' : 'none'; + }; + + let attachBulkButton = () => { + let actions = (el.parentElement ? el.parentElement.querySelector('.cbi-page-actions') : null) || document.querySelector('.cbi-page-actions'); + if (!actions) + return false; + + if (!actions.querySelector('.hp-bulk-latency-test')) + actions.insertBefore(bulk_button, actions.firstChild); + + updateBulkButtonVisibility(); + + if (!tabmenu_observer) { + let tabmenu = el.querySelector('.cbi-tabmenu'); + if (tabmenu) { + tabmenu_observer = new MutationObserver(() => updateBulkButtonVisibility()); + tabmenu_observer.observe(tabmenu, { + subtree: true, + attributes: true, + attributeFilter: [ 'class', 'aria-selected' ] + }); + } + } + + return true; + }; + + if (!attachBulkButton()) { + window.setTimeout(attachBulkButton, 0); + window.setTimeout(attachBulkButton, 300); + } + + return el; + }); } }); diff --git a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js index bc31014a9..e807ee579 100644 --- a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js +++ b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js @@ -126,6 +126,8 @@ return view.extend({ let m, s, o; let features = data[1]; + hp.installCloseButtonText(); + m = new form.Map('homeproxy', _('HomeProxy Server'), _('The modern ImmortalWrt proxy platform for ARM64/AMD64.')); @@ -188,6 +190,7 @@ return view.extend({ o.value('tuic', _('Tuic')); o.value('vless', _('VLESS')); o.value('vmess', _('VMess')); + o.default = 'mixed'; o.rmempty = false; o = s.option(form.Value, 'address', _('Listen address')); @@ -637,10 +640,10 @@ return view.extend({ o.modalonly = true; o = s.option(form.ListValue, 'tls_dns01_provider', _('DNS provider')); - o.value('alidns', _('Alibaba Cloud DNS')); - o.value('cloudflare', _('Cloudflare')); + o.value('alidns', _('AliYun')); + o.value('cfdns', _('CloudFlare')); o.depends('tls_dns01_challenge', '1'); - o.default = 'cloudflare'; + o.default = 'cfdns'; o.rmempty = false; o.modalonly = true; @@ -663,7 +666,7 @@ return view.extend({ o = s.option(form.Value, 'tls_dns01_cf_api_token', _('API token')); o.password = true; - o.depends('tls_dns01_provider', 'cloudflare'); + o.depends('tls_dns01_provider', 'cfdns'); o.rmempty = false; o.modalonly = true; diff --git a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js index f8a617000..3f2e801ca 100644 --- a/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js +++ b/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js @@ -14,6 +14,8 @@ 'require ui'; 'require view'; +'require homeproxy as hp'; + /* Thanks to luci-app-aria2 */ const css = ' \ #log_textarea { \ @@ -27,6 +29,53 @@ const css = ' \ } \ .description { \ background-color: #33ccff; \ +} \ +.homeproxy-connect-grid { \ + display: grid; \ + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));\ + gap: 12px; \ +} \ +.homeproxy-connect-card { \ + display: grid; \ + grid-template-columns: 44px 1fr; \ + gap: 10px; \ + align-items: center; \ + min-height: 64px; \ + padding: 8px 10px; \ + border-radius: 6px; \ + background: #fff; \ + box-shadow: 0 1px 4px rgba(0, 0, 0, .08);\ +} \ +.homeproxy-connect-icon { \ + display: flex; \ + align-items: center; \ + justify-content: center; \ + width: 38px; \ + height: 38px; \ + border-radius: 50%; \ + color: #fff; \ + font-weight: 700; \ + font-size: 15px; \ +} \ +.homeproxy-connect-info { \ + min-height: 42px; \ + padding: 7px 12px; \ + border-radius: 8px; \ + background: #e9edf2; \ +} \ +.homeproxy-connect-title { \ + color: #7d8aa0; \ + font-weight: 700; \ + font-size: 13px; \ + line-height: 1.2; \ +} \ +.homeproxy-connect-result { \ + margin-top: 2px; \ + color: #ff4d35; \ + font-weight: 700; \ + font-size: 13px; \ + cursor: pointer; \ + line-height: 1.2; \ }'; const hp_dir = '/var/run/homeproxy'; @@ -44,13 +93,13 @@ function getConnStat(o, site) { 'class': 'btn cbi-button cbi-button-action', 'click': ui.createHandlerFn(this, () => { return L.resolveDefault(callConnStat(site), {}).then((ret) => { - let ele = o.default.firstElementChild.nextElementSibling; + let ele = o.default.firstElementChild.nextElementSibling; if (ret.result) { ele.style.setProperty('color', 'green'); - ele.innerHTML = _('passed'); + ele.innerHTML = _('passed'); } else { ele.style.setProperty('color', 'red'); - ele.innerHTML = _('failed'); + ele.innerHTML = _('failed'); } }); }) @@ -60,6 +109,53 @@ function getConnStat(o, site) { ]); } +function renderConnCards() { + const callConnStat = rpc.declare({ + object: 'luci.homeproxy', + method: 'connection_check', + params: ['site'], + expect: { '': {} } + }); + + const items = [ + { site: 'baidu', title: _('Baidu Connection'), icon: 'B', color: '#ff7a00' }, + { site: 'google', title: _('Google Connection'), icon: 'G', color: '#4285f4' }, + { site: 'github', title: _('GitHub Connection'), icon: 'GH', color: '#7b3bb3' }, + { site: 'youtube', title: _('YouTube Connection'), icon: 'YT', color: '#ff0033' } + ]; + + return E('div', { 'class': 'homeproxy-connect-grid' }, items.map((item) => { + let result = E('div', { + 'class': 'homeproxy-connect-result', + 'click': ui.createHandlerFn(this, () => { + result.textContent = _('Checking...'); + result.style.setProperty('color', '#f0ad4e'); + + return L.resolveDefault(callConnStat(item.site), {}).then((ret) => { + if (ret.result && ret.latency != null) { + result.style.setProperty('color', 'green'); + result.textContent = _('%s ms').format(ret.latency); + } else { + result.style.setProperty('color', '#ff4d35'); + result.textContent = _('failed'); + } + }); + }) + }, [ _('Click to check') ]); + + return E('div', { 'class': 'homeproxy-connect-card' }, [ + E('div', { + 'class': 'homeproxy-connect-icon', + 'style': 'background:%s'.format(item.color) + }, [ item.icon ]), + E('div', { 'class': 'homeproxy-connect-info' }, [ + E('div', { 'class': 'homeproxy-connect-title' }, [ item.title ]), + result + ]) + ]); + })); +} + function getResVersion(o, type) { const callResVersion = rpc.declare({ object: 'luci.homeproxy', @@ -227,16 +323,16 @@ return view.extend({ render() { let m, s, o; + hp.installCloseButtonText(); + m = new form.Map('homeproxy'); s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check')); s.anonymous = true; - o = s.option(form.DummyValue, '_check_baidu', _('BaiDu')); - o.cfgvalue = L.bind(getConnStat, this, o, 'baidu'); - - o = s.option(form.DummyValue, '_check_google', _('Google')); - o.cfgvalue = L.bind(getConnStat, this, o, 'google'); + o = s.option(form.DummyValue, '_connection_check'); + o.rawhtml = true; + o.render = renderConnCards; s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management')); s.anonymous = true; diff --git a/luci-app-homeproxy/po/templates/homeproxy.pot b/luci-app-homeproxy/po/templates/homeproxy.pot index 0fa716554..daea34056 100644 --- a/luci-app-homeproxy/po/templates/homeproxy.pot +++ b/luci-app-homeproxy/po/templates/homeproxy.pot @@ -1,11 +1,19 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" +#: htdocs/luci-static/resources/homeproxy.js:321 +msgid "Close" +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/status.js:206 msgid "%s log" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1456 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +msgid "%s ms" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1896 msgid "%s nodes removed" msgstr "" @@ -13,19 +21,19 @@ msgstr "" msgid "/etc/homeproxy/certs/..., /etc/acme/..., /etc/ssl/..." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:609 +#: htdocs/luci-static/resources/view/homeproxy/client.js:632 msgid "4 or 6. Not limited if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:775 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1210 +#: htdocs/luci-static/resources/view/homeproxy/client.js:798 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1233 msgid "" "%s will be temporarily overwritten to %s after 50 " "triggers in 30s if not enabled." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1105 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1516 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1535 #: htdocs/luci-static/resources/view/homeproxy/server.js:768 #: htdocs/luci-static/resources/view/homeproxy/server.js:787 msgid "Save your configuration before uploading files!" @@ -35,15 +43,15 @@ msgstr "" msgid "API token" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:670 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1100 msgid "Accept any if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 msgid "Accept empty query response" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1399 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 msgid "Access Control" msgstr "" @@ -55,32 +63,36 @@ msgstr "" msgid "Access key secret" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:669 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1146 +#: htdocs/luci-static/resources/view/homeproxy/client.js:692 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1169 msgid "Action" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:210 +msgid "AdGuard Public DNS (DoH)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1079 msgid "Add a DNS rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "Add a DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 msgid "Add a node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:428 msgid "Add a routing node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/client.js:599 msgid "Add a routing rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1343 msgid "Add a rule set" msgstr "" @@ -88,33 +100,32 @@ msgstr "" msgid "Add a server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:975 +#: htdocs/luci-static/resources/view/homeproxy/client.js:998 msgid "Additional headers to be sent to the DNS server." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:957 -#: htdocs/luci-static/resources/view/homeproxy/node.js:448 +#: htdocs/luci-static/resources/view/homeproxy/client.js:980 +#: htdocs/luci-static/resources/view/homeproxy/node.js:878 msgid "Address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:988 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1011 msgid "Address resolver" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1020 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1043 msgid "Address strategy" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/server.js:640 -msgid "Alibaba Cloud DNS" +msgid "AliYun" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:186 -#: htdocs/luci-static/resources/view/homeproxy/client.js:220 -msgid "Aliyun Public DNS (223.5.5.5)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +msgid "AliYun Public DNS (DoH)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:266 +#: htdocs/luci-static/resources/view/homeproxy/client.js:289 msgid "All ports" msgstr "" @@ -122,20 +133,20 @@ msgstr "" msgid "Allow access from the Internet." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1040 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1382 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1470 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1822 msgid "Allow insecure" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1041 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1471 msgid "Allow insecure connection at TLS client." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1383 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1823 msgid "Allow insecure connection by default when add nodes from subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:874 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1304 #: htdocs/luci-static/resources/view/homeproxy/server.js:482 msgid "Allowed payload size is in the request." msgstr "" @@ -148,7 +159,7 @@ msgstr "" msgid "Already in updating." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1172 #: htdocs/luci-static/resources/view/homeproxy/server.js:391 msgid "Alter ID" msgstr "" @@ -161,15 +172,15 @@ msgstr "" msgid "Alternative TLS port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1419 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1859 msgid "An error occurred during updating subscriptions: %s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1250 msgid "Answer" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:429 +#: htdocs/luci-static/resources/view/homeproxy/node.js:859 #: htdocs/luci-static/resources/view/homeproxy/server.js:176 msgid "AnyTLS" msgstr "" @@ -178,29 +189,29 @@ msgstr "" msgid "AnyTLS padding scheme in array." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:799 -#: htdocs/luci-static/resources/view/homeproxy/client.js:909 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1195 +#: htdocs/luci-static/resources/view/homeproxy/client.js:822 +#: htdocs/luci-static/resources/view/homeproxy/client.js:932 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1218 msgid "" "Append a edns0-subnet OPT extra record with the specified IP " "prefix to every query by default.
If value is an IP address instead of " "prefix, /32 or /128 will be appended automatically." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1072 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1502 msgid "Append self-signed certificate" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:407 +#: htdocs/luci-static/resources/view/homeproxy/node.js:802 msgid "Applied" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:400 -#: htdocs/luci-static/resources/view/homeproxy/node.js:410 +#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/node.js:805 msgid "Apply" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:18 +#: htdocs/luci-static/resources/view/homeproxy/node.js:26 msgid "Are you sure to allow insecure?" msgstr "" @@ -208,90 +219,98 @@ msgstr "" msgid "Auth timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:766 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1196 msgid "Authenticated length" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:991 #: htdocs/luci-static/resources/view/homeproxy/server.js:282 msgid "Authentication payload" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:554 +#: htdocs/luci-static/resources/view/homeproxy/node.js:984 #: htdocs/luci-static/resources/view/homeproxy/server.js:275 msgid "Authentication type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:703 +msgid "Auto select" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1768 msgid "Auto update" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1336 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1769 msgid "Auto update subscriptions and geodata." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:702 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1132 msgid "BBR" msgstr "" +#: htdocs/luci-static/resources/view/homeproxy/node.js:472 +msgid "Backend execution failed" +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/status.js:235 msgid "BaiDu" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:556 +#: htdocs/luci-static/resources/view/homeproxy/node.js:986 #: htdocs/luci-static/resources/view/homeproxy/server.js:277 msgid "Base64" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:323 +#: htdocs/luci-static/resources/view/homeproxy/client.js:346 msgid "Based on google/gvisor." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1341 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1364 msgid "Binary file" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:453 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1412 +#: htdocs/luci-static/resources/view/homeproxy/client.js:476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1435 #: htdocs/luci-static/resources/view/homeproxy/server.js:874 msgid "Bind interface" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1413 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1436 msgid "" "Bind outbound traffic to specific interface. Leave empty to auto detect." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:617 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 +#: htdocs/luci-static/resources/view/homeproxy/client.js:640 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1129 msgid "BitTorrent" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1369 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1809 msgid "Blacklist mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:365 +#: htdocs/luci-static/resources/view/homeproxy/client.js:388 msgid "Block" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:612 -#: htdocs/luci-static/resources/view/homeproxy/client.js:640 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1092 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1102 +#: htdocs/luci-static/resources/view/homeproxy/client.js:635 +#: htdocs/luci-static/resources/view/homeproxy/client.js:663 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 #: htdocs/luci-static/resources/view/homeproxy/server.js:869 msgid "Both" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:343 +#: htdocs/luci-static/resources/view/homeproxy/client.js:366 msgid "Bypass CN traffic" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:276 msgid "Bypass mainland China" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:344 +#: htdocs/luci-static/resources/view/homeproxy/client.js:367 msgid "Bypass mainland China traffic via firewall rules by default." msgstr "" @@ -299,19 +318,15 @@ msgstr "" msgid "CA provider" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:221 -msgid "CNNIC Public DNS (210.2.4.8)" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:700 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1130 msgid "CUBIC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1239 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1672 msgid "Cancel" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1077 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1507 #: htdocs/luci-static/resources/view/homeproxy/server.js:756 msgid "Certificate path" msgstr "" @@ -324,7 +339,7 @@ msgstr "" msgid "Check update" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:217 +#: htdocs/luci-static/resources/view/homeproxy/client.js:241 msgid "China DNS server" msgstr "" @@ -340,24 +355,28 @@ msgstr "" msgid "China list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:629 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1783 +msgid "Choose which method node latency tests use." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:652 msgid "Chromium / Cronet" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1064 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1494 #: htdocs/luci-static/resources/view/homeproxy/server.js:586 msgid "Cipher suites" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:183 -msgid "Cisco Public DNS (208.67.222.222)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:212 +msgid "Cisco Public DNS (DoH)" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/status.js:214 msgid "Clean log" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:627 +#: htdocs/luci-static/resources/view/homeproxy/client.js:650 msgid "Client" msgstr "" @@ -365,29 +384,29 @@ msgstr "" msgid "Client Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:664 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1094 msgid "Client version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:182 -msgid "CloudFlare Public DNS (1.1.1.1)" -msgstr "" - #: htdocs/luci-static/resources/view/homeproxy/server.js:641 -msgid "Cloudflare" +msgid "CloudFlare" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:114 +#: htdocs/luci-static/resources/view/homeproxy/client.js:207 +msgid "CloudFlare Public DNS (DoH)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:139 #: htdocs/luci-static/resources/view/homeproxy/server.js:142 #: htdocs/luci-static/resources/view/homeproxy/status.js:176 msgid "Collecting data..." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:267 +#: htdocs/luci-static/resources/view/homeproxy/client.js:290 msgid "Common ports only (bypass P2P traffic)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:698 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1128 #: htdocs/luci-static/resources/view/homeproxy/server.js:354 msgid "Congestion control algorithm" msgstr "" @@ -396,23 +415,27 @@ msgstr "" msgid "Connection check" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:255 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1772 +msgid "Cron expression" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:278 msgid "Custom routing" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:641 msgid "DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1047 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1070 msgid "DNS Rules" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:924 +#: htdocs/luci-static/resources/view/homeproxy/client.js:947 msgid "DNS Servers" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:895 msgid "DNS Settings" msgstr "" @@ -420,22 +443,26 @@ msgstr "" msgid "DNS provider" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1079 msgid "DNS rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:179 -#: htdocs/luci-static/resources/view/homeproxy/client.js:749 -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:204 +#: htdocs/luci-static/resources/view/homeproxy/client.js:772 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "DNS server" msgstr "" +#: htdocs/luci-static/resources/view/homeproxy/client.js:211 +msgid "DNS.SB Public DNS (DoH)" +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/server.js:635 msgid "DNS01 challenge" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:619 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1107 +#: htdocs/luci-static/resources/view/homeproxy/client.js:642 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 msgid "DTLS" msgstr "" @@ -444,49 +471,56 @@ msgid "Debug" msgstr "" #: htdocs/luci-static/resources/homeproxy.js:17 -#: htdocs/luci-static/resources/view/homeproxy/client.js:433 -#: htdocs/luci-static/resources/view/homeproxy/client.js:603 -#: htdocs/luci-static/resources/view/homeproxy/client.js:755 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1083 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1381 -#: htdocs/luci-static/resources/view/homeproxy/node.js:710 +#: htdocs/luci-static/resources/view/homeproxy/client.js:456 +#: htdocs/luci-static/resources/view/homeproxy/client.js:626 +#: htdocs/luci-static/resources/view/homeproxy/client.js:778 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1404 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1140 msgid "Default" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:382 -#: htdocs/luci-static/resources/view/homeproxy/client.js:434 -#: htdocs/luci-static/resources/view/homeproxy/client.js:756 -#: htdocs/luci-static/resources/view/homeproxy/client.js:887 -#: htdocs/luci-static/resources/view/homeproxy/client.js:995 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 +msgid "Default node" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:457 +#: htdocs/luci-static/resources/view/homeproxy/client.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:910 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1018 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1184 msgid "Default DNS (issued by WAN)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:905 msgid "Default DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:377 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1332 +msgid "Daily domain name resolution for clients. Choose an overseas DNS if DNS leak protection is required." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:400 msgid "Default DNS server for resolving domain name in the server address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:900 msgid "Default DNS strategy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:357 -msgid "Default outbound" +#: htdocs/luci-static/resources/view/homeproxy/client.js:380 +msgid "Default outbound (fallback)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:376 +#: htdocs/luci-static/resources/view/homeproxy/client.js:399 msgid "Default outbound DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:358 +#: htdocs/luci-static/resources/view/homeproxy/client.js:381 msgid "Default outbound for connections not matched by any routing rules." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1389 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1829 msgid "Default packet encoding" msgstr "" @@ -494,64 +528,79 @@ msgstr "" msgid "Default server name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:364 -#: htdocs/luci-static/resources/view/homeproxy/client.js:466 -#: htdocs/luci-static/resources/view/homeproxy/client.js:684 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1033 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1382 -#: htdocs/luci-static/resources/view/homeproxy/node.js:428 +#: htdocs/luci-static/resources/view/homeproxy/client.js:387 +#: htdocs/luci-static/resources/view/homeproxy/client.js:489 +#: htdocs/luci-static/resources/view/homeproxy/client.js:707 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1405 +#: htdocs/luci-static/resources/view/homeproxy/node.js:858 msgid "Direct" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1514 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1537 msgid "Direct Domain List" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1428 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1473 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1451 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1496 msgid "Direct IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1431 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1499 msgid "Direct IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1434 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1457 msgid "Direct MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:123 -#: htdocs/luci-static/resources/view/homeproxy/client.js:151 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 -#: htdocs/luci-static/resources/view/homeproxy/node.js:499 -#: htdocs/luci-static/resources/view/homeproxy/node.js:555 -#: htdocs/luci-static/resources/view/homeproxy/node.js:568 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1115 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1368 +#: htdocs/luci-static/resources/view/homeproxy/client.js:148 +#: htdocs/luci-static/resources/view/homeproxy/client.js:176 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:929 +#: htdocs/luci-static/resources/view/homeproxy/node.js:985 +#: htdocs/luci-static/resources/view/homeproxy/node.js:998 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1545 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1808 #: htdocs/luci-static/resources/view/homeproxy/server.js:276 #: htdocs/luci-static/resources/view/homeproxy/server.js:289 msgid "Disable" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:363 +#: htdocs/luci-static/resources/view/homeproxy/client.js:386 msgid "Disable (the service)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:787 -#: htdocs/luci-static/resources/view/homeproxy/client.js:899 +#: htdocs/luci-static/resources/view/homeproxy/client.js:810 +#: htdocs/luci-static/resources/view/homeproxy/client.js:922 msgid "Disable DNS cache" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:788 +#: htdocs/luci-static/resources/view/homeproxy/client.js:811 msgid "Disable DNS cache in this query." msgstr "" +msgid "Disable DNS memory cache" +msgstr "" + +msgid "Disable DNS memory cache in this query." +msgstr "" + +msgid "Disable DNS memory cache expiration" +msgstr "" + +msgid "Independent DNS memory cache per server" +msgstr "" + +msgid "Make each DNS server's memory cache independent for special purposes. If enabled, will slightly degrade performance." +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/server.js:670 msgid "Disable HTTP challenge" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:605 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1035 #: htdocs/luci-static/resources/view/homeproxy/server.js:321 msgid "Disable Path MTU discovery" msgstr "" @@ -560,60 +609,63 @@ msgstr "" msgid "Disable TLS ALPN challenge" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:710 +#: htdocs/luci-static/resources/view/homeproxy/client.js:733 msgid "Disable UDP domain unmapping" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1182 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1205 msgid "Disable cache and save cache in this query." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:901 +msgid "Disable DNS memory cache and persistent cache in this query." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:924 msgid "Disable cache expire" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1181 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 msgid "Disable dns cache" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:606 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1036 #: htdocs/luci-static/resources/view/homeproxy/server.js:322 msgid "" "Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 " "(IPv4) / 1232 (IPv6) bytes in size." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:814 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1251 +#: htdocs/luci-static/resources/view/homeproxy/client.js:837 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1274 msgid "Domain keyword" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:805 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:828 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1265 msgid "Domain name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:818 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1255 +#: htdocs/luci-static/resources/view/homeproxy/client.js:841 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1278 msgid "Domain regex" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:427 +#: htdocs/luci-static/resources/view/homeproxy/client.js:450 msgid "Domain resolver" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:347 -#: htdocs/luci-static/resources/view/homeproxy/client.js:446 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1174 +#: htdocs/luci-static/resources/view/homeproxy/client.js:370 +#: htdocs/luci-static/resources/view/homeproxy/client.js:469 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1197 msgid "Domain strategy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:781 +#: htdocs/luci-static/resources/view/homeproxy/client.js:804 msgid "Domain strategy for resolving the domain names." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:810 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1247 +#: htdocs/luci-static/resources/view/homeproxy/client.js:833 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1270 msgid "Domain suffix" msgstr "" @@ -621,42 +673,42 @@ msgstr "" msgid "Domains" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:797 msgid "Don't drop packets" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1209 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1232 msgid "Don't drop requests" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:989 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1419 #: htdocs/luci-static/resources/view/homeproxy/server.js:517 msgid "Download bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:990 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1420 #: htdocs/luci-static/resources/view/homeproxy/server.js:518 msgid "Download bandwidth in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:770 -#: htdocs/luci-static/resources/view/homeproxy/client.js:776 +#: htdocs/luci-static/resources/view/homeproxy/client.js:793 +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 msgid "Drop packets" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1234 msgid "Drop requests" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1375 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1815 msgid "" "Drop/keep nodes that contain the specific keywords. Regex is supported." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1367 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1807 msgid "Drop/keep specific nodes from subscriptions." msgstr "" @@ -668,18 +720,18 @@ msgid "" "a non-ACME system, such as a CA customer database." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1094 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1524 msgid "" "ECH (Encrypted Client Hello) is a TLS extension that allows a client to " "encrypt the first part of its ClientHello message." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1109 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1539 #: htdocs/luci-static/resources/view/homeproxy/server.js:835 msgid "ECH config" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1098 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1528 msgid "ECH config path" msgstr "" @@ -687,18 +739,18 @@ msgstr "" msgid "ECH key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:798 -#: htdocs/luci-static/resources/view/homeproxy/client.js:908 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1194 +#: htdocs/luci-static/resources/view/homeproxy/client.js:821 +#: htdocs/luci-static/resources/view/homeproxy/client.js:931 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1217 msgid "EDNS Client subnet" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:873 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1303 #: htdocs/luci-static/resources/view/homeproxy/server.js:481 msgid "Early data" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:880 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1310 #: htdocs/luci-static/resources/view/homeproxy/server.js:488 msgid "Early data header name" msgstr "" @@ -707,7 +759,7 @@ msgstr "" msgid "Early data is sent in path instead of header by default." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1211 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1644 msgid "Edit nodes" msgstr "" @@ -715,17 +767,17 @@ msgstr "" msgid "Email" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:414 -#: htdocs/luci-static/resources/view/homeproxy/client.js:590 -#: htdocs/luci-static/resources/view/homeproxy/client.js:942 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1070 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1329 +#: htdocs/luci-static/resources/view/homeproxy/client.js:437 +#: htdocs/luci-static/resources/view/homeproxy/client.js:613 +#: htdocs/luci-static/resources/view/homeproxy/client.js:965 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1352 #: htdocs/luci-static/resources/view/homeproxy/server.js:148 #: htdocs/luci-static/resources/view/homeproxy/server.js:166 msgid "Enable" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:722 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1152 #: htdocs/luci-static/resources/view/homeproxy/server.js:371 msgid "" "Enable 0-RTT QUIC connection handshake on the client side. This is not " @@ -733,7 +785,7 @@ msgid "" ">Disabling this is highly recommended, as it is vulnerable to replay attacks." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:721 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1151 #: htdocs/luci-static/resources/view/homeproxy/server.js:370 msgid "Enable 0-RTT handshake" msgstr "" @@ -742,30 +794,30 @@ msgstr "" msgid "Enable ACME" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1523 msgid "Enable ECH" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:984 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1414 #: htdocs/luci-static/resources/view/homeproxy/server.js:512 msgid "Enable TCP Brutal" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:985 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1415 #: htdocs/luci-static/resources/view/homeproxy/server.js:513 msgid "Enable TCP Brutal congestion control algorithm" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1168 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1598 #: htdocs/luci-static/resources/view/homeproxy/server.js:855 msgid "Enable UDP fragmentation." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:328 +#: htdocs/luci-static/resources/view/homeproxy/client.js:351 msgid "Enable endpoint-independent NAT" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:980 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1410 #: htdocs/luci-static/resources/view/homeproxy/server.js:507 msgid "Enable padding" msgstr "" @@ -774,17 +826,18 @@ msgstr "" msgid "Enable tcp fast open for listener." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1172 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1602 msgid "" "Enable the SUoT protocol, requires server support. Conflict with multiplex." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:612 -#: htdocs/luci-static/resources/view/homeproxy/node.js:748 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1042 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 #: htdocs/luci-static/resources/view/homeproxy/server.js:338 msgid "Encrypt method" msgstr "" +#: htdocs/luci-static/resources/view/homeproxy/node.js:488 #: htdocs/luci-static/resources/view/homeproxy/status.js:139 msgid "Error" msgstr "" @@ -797,26 +850,26 @@ msgstr "" #: htdocs/luci-static/resources/homeproxy.js:317 #: htdocs/luci-static/resources/homeproxy.js:326 #: htdocs/luci-static/resources/homeproxy.js:328 -#: htdocs/luci-static/resources/view/homeproxy/client.js:195 -#: htdocs/luci-static/resources/view/homeproxy/client.js:207 -#: htdocs/luci-static/resources/view/homeproxy/client.js:211 -#: htdocs/luci-static/resources/view/homeproxy/client.js:230 -#: htdocs/luci-static/resources/view/homeproxy/client.js:241 -#: htdocs/luci-static/resources/view/homeproxy/client.js:245 -#: htdocs/luci-static/resources/view/homeproxy/client.js:274 -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:518 -#: htdocs/luci-static/resources/view/homeproxy/client.js:521 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1362 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1539 -#: htdocs/luci-static/resources/view/homeproxy/node.js:488 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1302 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1359 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:231 +#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:268 +#: htdocs/luci-static/resources/view/homeproxy/client.js:297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:527 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1380 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1562 +#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1735 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1796 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1799 #: htdocs/luci-static/resources/view/homeproxy/server.js:235 #: htdocs/luci-static/resources/view/homeproxy/server.js:618 #: htdocs/luci-static/resources/view/homeproxy/server.js:620 @@ -835,7 +888,7 @@ msgstr "" msgid "External account key ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1237 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1260 msgid "Extra records" msgstr "" @@ -847,19 +900,53 @@ msgstr "" msgid "Failed to upload %s, error: %s." msgstr "" +#: htdocs/luci-static/resources/homeproxy.js +msgid "panel ZIP package" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "unknown error" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Backup panel failed." +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Restore panel failed." +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Panel ZIP file is empty." +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "The unzip command is unavailable." +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Invalid panel ZIP file." +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Install panel failed." +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/status.js:140 msgid "Fatal" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1374 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1814 msgid "Filter keywords" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1806 msgid "Filter nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:630 +#: htdocs/luci-static/resources/view/homeproxy/client.js:653 msgid "Firefox / uquic firefox" msgstr "" @@ -867,33 +954,33 @@ msgstr "" msgid "Firewall" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:736 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1166 #: htdocs/luci-static/resources/view/homeproxy/server.js:385 msgid "Flow" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:428 +#: htdocs/luci-static/resources/view/homeproxy/client.js:451 msgid "For resolving domain name in the server address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1340 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1363 msgid "Format" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:730 +#: htdocs/luci-static/resources/view/homeproxy/client.js:753 msgid "Fragment TLS handshake into multiple TLS records." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:736 +#: htdocs/luci-static/resources/view/homeproxy/client.js:759 msgid "" "Fragment TLS handshakes. Due to poor performance, try %s first." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:742 +#: htdocs/luci-static/resources/view/homeproxy/client.js:765 msgid "Fragment fallback delay" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:842 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1272 msgid "GET" msgstr "" @@ -901,19 +988,19 @@ msgstr "" msgid "GFW list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:252 +#: htdocs/luci-static/resources/view/homeproxy/client.js:275 msgid "GFWList" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1446 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1469 msgid "Gaming mode IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1448 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1471 msgid "Gaming mode IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1451 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1474 msgid "Gaming mode MAC-s" msgstr "" @@ -927,23 +1014,23 @@ msgstr "" msgid "GitHub token" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:279 msgid "Global" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:759 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1189 msgid "Global padding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1453 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1476 msgid "Global proxy IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1456 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1479 msgid "Global proxy IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1459 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1482 msgid "Global proxy MAC-s" msgstr "" @@ -955,24 +1042,24 @@ msgstr "" msgid "Google" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:184 -msgid "Google Public DNS (8.8.8.8)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:208 +msgid "Google Public DNS (DoH)" msgstr "" #: root/usr/share/rpcd/acl.d/luci-app-homeproxy.json:3 msgid "Grant access to homeproxy configuration" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:620 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1108 -#: htdocs/luci-static/resources/view/homeproxy/node.js:430 -#: htdocs/luci-static/resources/view/homeproxy/node.js:777 +#: htdocs/luci-static/resources/view/homeproxy/client.js:643 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/node.js:860 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1207 #: htdocs/luci-static/resources/view/homeproxy/server.js:177 #: htdocs/luci-static/resources/view/homeproxy/server.js:403 msgid "HTTP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:952 +#: htdocs/luci-static/resources/view/homeproxy/client.js:975 msgid "HTTP/3" msgstr "" @@ -982,11 +1069,11 @@ msgid "" "returned if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:974 msgid "HTTPS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:778 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1208 #: htdocs/luci-static/resources/view/homeproxy/server.js:404 msgid "HTTPUpgrade" msgstr "" @@ -999,18 +1086,18 @@ msgstr "" msgid "Handshake server port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:974 +#: htdocs/luci-static/resources/view/homeproxy/client.js:997 msgid "Headers" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:727 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1157 #: htdocs/luci-static/resources/view/homeproxy/server.js:376 msgid "Heartbeat interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/client.js:63 +#: htdocs/luci-static/resources/view/homeproxy/client.js:65 +#: htdocs/luci-static/resources/view/homeproxy/client.js:112 #: htdocs/luci-static/resources/view/homeproxy/status.js:282 #: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3 msgid "HomeProxy" @@ -1022,33 +1109,33 @@ msgstr "" msgid "HomeProxy Server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:535 +#: htdocs/luci-static/resources/view/homeproxy/node.js:965 msgid "Hop interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:529 +#: htdocs/luci-static/resources/view/homeproxy/node.js:959 msgid "Hopping port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:831 -#: htdocs/luci-static/resources/view/homeproxy/node.js:865 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1256 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1261 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1295 #: htdocs/luci-static/resources/view/homeproxy/server.js:437 #: htdocs/luci-static/resources/view/homeproxy/server.js:442 #: htdocs/luci-static/resources/view/homeproxy/server.js:473 msgid "Host" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:669 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1099 msgid "Host key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:674 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1104 msgid "Host key algorithms" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:581 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1061 +#: htdocs/luci-static/resources/view/homeproxy/client.js:604 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1084 msgid "Host/IP fields" msgstr "" @@ -1058,28 +1145,44 @@ msgid "" "command (in seconds)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:432 +#: htdocs/luci-static/resources/view/homeproxy/node.js:862 #: htdocs/luci-static/resources/view/homeproxy/server.js:179 msgid "Hysteria" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:433 +#: htdocs/luci-static/resources/view/homeproxy/node.js:863 #: htdocs/luci-static/resources/view/homeproxy/server.js:180 msgid "Hysteria2" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:830 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1267 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1784 +msgid "ICMP" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:474 +msgid "ICMP Failed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:476 +msgid "ICMP Probe unavailable" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:478 +msgid "ICMP Timeout" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:853 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1290 msgid "IP CIDR" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:608 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +#: htdocs/luci-static/resources/view/homeproxy/client.js:631 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 msgid "IP version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:610 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/client.js:633 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 msgid "IPv4" msgstr "" @@ -1087,8 +1190,8 @@ msgstr "" msgid "IPv4 only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:611 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1091 +#: htdocs/luci-static/resources/view/homeproxy/client.js:634 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1114 msgid "IPv6" msgstr "" @@ -1096,55 +1199,55 @@ msgstr "" msgid "IPv6 only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:320 msgid "IPv6 support" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:506 +#: htdocs/luci-static/resources/view/homeproxy/node.js:936 msgid "Idle session check interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:513 +#: htdocs/luci-static/resources/view/homeproxy/node.js:943 msgid "Idle session check timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:553 -#: htdocs/luci-static/resources/view/homeproxy/node.js:847 +#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1277 #: htdocs/luci-static/resources/view/homeproxy/server.js:456 msgid "Idle timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:717 +#: htdocs/luci-static/resources/view/homeproxy/client.js:740 msgid "" "If enabled, attempts to connect UDP connection to the destination instead of " "listen." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:711 +#: htdocs/luci-static/resources/view/homeproxy/client.js:734 msgid "" "If enabled, for UDP proxy requests addressed to a domain, the original " "packet address will be sent in the response instead of the mapped domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:819 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1249 msgid "" "If enabled, the client transport sends keepalive pings even with no active " "connections." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:348 +#: htdocs/luci-static/resources/view/homeproxy/client.js:371 msgid "" "If set, the requested domain name will be resolved to IP before routing." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:804 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1234 #: htdocs/luci-static/resources/view/homeproxy/server.js:425 msgid "" "If the transport doesn't see any activity after a duration of this time (in " "seconds), it pings the client to check if the connection is still active." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1073 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1503 msgid "" "If you have the root certificate, use this option instead of allowing " "insecure." @@ -1154,37 +1257,37 @@ msgstr "" msgid "Ignore client bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1285 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1718 msgid "Import" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1232 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1313 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1665 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1744 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1746 msgid "Import share links" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:336 +#: htdocs/luci-static/resources/view/homeproxy/client.js:359 #: htdocs/luci-static/resources/view/homeproxy/server.js:860 msgid "In seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:937 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1367 msgid "In seconds. Disabled by default." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:521 +#: htdocs/luci-static/resources/view/homeproxy/node.js:951 msgid "" "In the check, at least the first n idle sessions are kept open." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:514 +#: htdocs/luci-static/resources/view/homeproxy/node.js:944 msgid "" "In the check, close sessions that have been idle for longer than this, in " "seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:904 +#: htdocs/luci-static/resources/view/homeproxy/client.js:927 msgid "Independent cache per server" msgstr "" @@ -1192,36 +1295,45 @@ msgstr "" msgid "Info" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1428 msgid "Interface Control" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:560 +#: htdocs/luci-static/resources/view/homeproxy/client.js:583 msgid "Interrupt existing connections" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:561 +#: htdocs/luci-static/resources/view/homeproxy/client.js:584 msgid "Interrupt existing connections when the selected outbound has changed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:507 +#: htdocs/luci-static/resources/view/homeproxy/node.js:937 msgid "Interval checking for idle sessions, in seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:728 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1158 #: htdocs/luci-static/resources/view/homeproxy/server.js:377 msgid "" "Interval for sending heartbeat packets for keeping the connection alive (in " "seconds)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:665 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:128 +#: htdocs/luci-static/resources/view/homeproxy/node.js:480 +msgid "Invalid node" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:482 +msgid "Invalid response" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:688 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1165 msgid "Invert" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:666 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 +#: htdocs/luci-static/resources/view/homeproxy/client.js:689 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1166 msgid "Invert match result." msgstr "" @@ -1229,28 +1341,36 @@ msgstr "" msgid "Key path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1419 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1442 msgid "LAN IP Policy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:409 -#: htdocs/luci-static/resources/view/homeproxy/client.js:585 -#: htdocs/luci-static/resources/view/homeproxy/client.js:937 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1065 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1324 -#: htdocs/luci-static/resources/view/homeproxy/node.js:422 +#: htdocs/luci-static/resources/view/homeproxy/client.js:432 +#: htdocs/luci-static/resources/view/homeproxy/client.js:608 +#: htdocs/luci-static/resources/view/homeproxy/client.js:960 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1347 +#: htdocs/luci-static/resources/view/homeproxy/node.js:852 #: htdocs/luci-static/resources/view/homeproxy/server.js:160 msgid "Label" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:743 +#: htdocs/luci-static/resources/view/homeproxy/node.js:817 +msgid "Latency" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1782 +msgid "Latency test mode" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1173 #: htdocs/luci-static/resources/view/homeproxy/server.js:392 msgid "" "Legacy protocol support (VMess MD5 Authentication) is provided for " "compatibility purposes only, use of alterId > 1 is not recommended." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:325 +#: htdocs/luci-static/resources/view/homeproxy/client.js:348 msgid "Less compatibility and sometimes better performance." msgstr "" @@ -1258,31 +1378,31 @@ msgstr "" msgid "Let's Encrypt" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:897 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1327 msgid "" "List of IP (v4 or v6) addresses prefixes to be assigned to the interface." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:132 -#: htdocs/luci-static/resources/view/homeproxy/client.js:161 -#: htdocs/luci-static/resources/view/homeproxy/client.js:497 +#: htdocs/luci-static/resources/view/homeproxy/client.js:157 +#: htdocs/luci-static/resources/view/homeproxy/client.js:186 +#: htdocs/luci-static/resources/view/homeproxy/client.js:520 msgid "List of nodes to test." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1036 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1466 #: htdocs/luci-static/resources/view/homeproxy/server.js:566 msgid "List of supported application level protocols, in order of preference." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1228 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1251 msgid "List of text DNS record to respond as answers." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1238 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1261 msgid "List of text DNS record to respond as extra records." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1233 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1256 msgid "List of text DNS record to respond as name servers." msgstr "" @@ -1290,7 +1410,7 @@ msgstr "" msgid "Listen address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1407 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1430 msgid "Listen interfaces" msgstr "" @@ -1302,11 +1422,11 @@ msgstr "" msgid "Loading" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1358 msgid "Local" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:896 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1326 msgid "Local address" msgstr "" @@ -1318,31 +1438,31 @@ msgstr "" msgid "Log is empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:930 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1360 msgid "MTU" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:150 +#: htdocs/luci-static/resources/view/homeproxy/client.js:175 msgid "Main UDP node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:122 +#: htdocs/luci-static/resources/view/homeproxy/client.js:147 msgid "Main node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:662 +#: htdocs/luci-static/resources/view/homeproxy/client.js:685 msgid "Make IP CIDR in rule set used to match the source IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1158 msgid "Make IP CIDR in rule sets match the source IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 msgid "Make IP CIDR in rule-sets accept empty query response." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:905 +#: htdocs/luci-static/resources/view/homeproxy/client.js:928 msgid "" "Make each DNS server's cache independent for special purposes. If enabled, " "will slightly degrade performance." @@ -1352,109 +1472,109 @@ msgstr "" msgid "Masquerade" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1268 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1291 msgid "" "Match IP CIDR with query response. Current rule will be skipped if not match." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:831 +#: htdocs/luci-static/resources/view/homeproxy/client.js:854 msgid "Match IP CIDR." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:811 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1248 +#: htdocs/luci-static/resources/view/homeproxy/client.js:834 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 msgid "Match domain suffix." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:815 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1252 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1275 msgid "Match domain using keyword." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:819 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:842 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1279 msgid "Match domain using regular expression." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:806 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1243 +#: htdocs/luci-static/resources/view/homeproxy/client.js:829 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1266 msgid "Match full domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:854 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1292 +#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1315 msgid "Match port range. Format as START:/:END/START:END." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:849 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1287 +#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1310 msgid "Match port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:835 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1272 +#: htdocs/luci-static/resources/view/homeproxy/client.js:858 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1295 msgid "Match private IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1273 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1296 msgid "Match private IP with query response." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:827 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:850 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1287 msgid "Match private source IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:859 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 msgid "Match process name." msgstr "" +#: htdocs/luci-static/resources/view/homeproxy/client.js:890 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1328 +msgid "Match process path using regular expression." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:886 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1324 +msgid "Match process path." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1119 +msgid "Match query type." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:670 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 +msgid "Match rule set." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:846 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1283 +msgid "Match source IP CIDR." +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/client.js:867 #: htdocs/luci-static/resources/view/homeproxy/client.js:1305 -msgid "Match process path using regular expression." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:863 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1301 -msgid "Match process path." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:1096 -msgid "Match query type." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:647 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1120 -msgid "Match rule set." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:823 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1260 -msgid "Match source IP CIDR." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:844 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1282 msgid "Match source port range. Format as START:/:END/START:END." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:839 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1277 +#: htdocs/luci-static/resources/view/homeproxy/client.js:862 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1300 msgid "Match source port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:643 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1116 +#: htdocs/luci-static/resources/view/homeproxy/client.js:666 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1139 msgid "Match user name." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:579 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1009 #: htdocs/luci-static/resources/view/homeproxy/server.js:261 msgid "Max download speed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:580 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1010 #: htdocs/luci-static/resources/view/homeproxy/server.js:262 msgid "Max download speed in Mbps." msgstr "" @@ -1463,106 +1583,110 @@ msgstr "" msgid "Max time difference" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:586 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1016 #: htdocs/luci-static/resources/view/homeproxy/server.js:268 msgid "Max upload speed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:587 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1017 #: htdocs/luci-static/resources/view/homeproxy/server.js:269 msgid "Max upload speed in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1486 #: htdocs/luci-static/resources/view/homeproxy/server.js:578 msgid "Maximum TLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:961 -#: htdocs/luci-static/resources/view/homeproxy/node.js:975 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1391 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1405 msgid "Maximum connections" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:973 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1403 msgid "" "Maximum multiplexed streams in a connection before opening a new connection." "
Conflict with %s and %s." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:972 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1402 msgid "Maximum streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:768 -#: htdocs/luci-static/resources/view/homeproxy/client.js:776 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 -#: htdocs/luci-static/resources/view/homeproxy/node.js:841 +#: htdocs/luci-static/resources/view/homeproxy/client.js:791 +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1225 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1234 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1271 #: htdocs/luci-static/resources/view/homeproxy/server.js:452 msgid "Method" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1048 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1478 #: htdocs/luci-static/resources/view/homeproxy/server.js:570 msgid "Minimum TLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:520 +#: htdocs/luci-static/resources/view/homeproxy/node.js:950 msgid "Minimum idle sessions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:967 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1397 msgid "" "Minimum multiplexed streams in a connection before opening a new connection." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:966 -#: htdocs/luci-static/resources/view/homeproxy/node.js:975 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1405 msgid "Minimum streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:310 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1773 +msgid "Minutes(0-59) Hours(0-23) Dates(1-31) Months(1-12) Weeks(0-6)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:333 #: htdocs/luci-static/resources/view/homeproxy/server.js:183 msgid "Mixed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:321 +#: htdocs/luci-static/resources/view/homeproxy/client.js:344 msgid "Mixed system TCP stack and gVisor UDP stack." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:595 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1075 +#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1098 msgid "Mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1164 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1594 #: htdocs/luci-static/resources/view/homeproxy/server.js:850 msgid "MultiPath TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:944 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1374 #: htdocs/luci-static/resources/view/homeproxy/server.js:500 msgid "Multiplex" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:952 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1382 msgid "Multiplex protocol." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 +#: htdocs/luci-static/resources/view/homeproxy/client.js:65 #: htdocs/luci-static/resources/view/homeproxy/server.js:60 msgid "NOT RUNNING" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1395 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1835 msgid "NOTE: Save current settings before updating subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1232 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1255 msgid "NS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:711 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1141 msgid "Native" msgstr "" @@ -1570,42 +1694,42 @@ msgstr "" msgid "NaïveProxy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:637 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1099 +#: htdocs/luci-static/resources/view/homeproxy/client.js:660 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 #: htdocs/luci-static/resources/view/homeproxy/server.js:866 msgid "Network" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:701 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 msgid "New Reno" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:774 -#: htdocs/luci-static/resources/view/homeproxy/node.js:791 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1204 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1221 #: htdocs/luci-static/resources/view/homeproxy/server.js:400 #: htdocs/luci-static/resources/view/homeproxy/server.js:417 msgid "No TCP transport, plain HTTP is merged into the HTTP transport." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:789 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1219 #: htdocs/luci-static/resources/view/homeproxy/server.js:415 msgid "No additional encryption support: It's basically duplicate encryption." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1412 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1852 msgid "No subscription available" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1877 msgid "No subscription node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1271 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1704 msgid "No valid share link found." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:419 -#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 msgid "Node" msgstr "" @@ -1613,81 +1737,86 @@ msgstr "" msgid "Node Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1217 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1650 msgid "Nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:994 -#: htdocs/luci-static/resources/view/homeproxy/node.js:737 -#: htdocs/luci-static/resources/view/homeproxy/node.js:775 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1167 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1205 #: htdocs/luci-static/resources/view/homeproxy/server.js:386 #: htdocs/luci-static/resources/view/homeproxy/server.js:401 msgid "None" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:573 +#: htdocs/luci-static/resources/view/homeproxy/node.js:494 +#: htdocs/luci-static/resources/view/homeproxy/node.js:507 +msgid "Not Tested" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1003 #: htdocs/luci-static/resources/view/homeproxy/server.js:294 msgid "Obfuscate password" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:567 +#: htdocs/luci-static/resources/view/homeproxy/node.js:997 #: htdocs/luci-static/resources/view/homeproxy/server.js:288 msgid "Obfuscate type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1408 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1431 msgid "Only process traffic from specific interfaces. Leave empty for all." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:254 +#: htdocs/luci-static/resources/view/homeproxy/client.js:277 msgid "Only proxy mainland China" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:580 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1060 +#: htdocs/luci-static/resources/view/homeproxy/client.js:603 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1083 msgid "Other fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:460 -#: htdocs/luci-static/resources/view/homeproxy/client.js:678 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1027 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1375 +#: htdocs/luci-static/resources/view/homeproxy/client.js:483 +#: htdocs/luci-static/resources/view/homeproxy/client.js:701 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1050 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1398 msgid "Outbound" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:420 +#: htdocs/luci-static/resources/view/homeproxy/client.js:443 msgid "Outbound node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:696 +#: htdocs/luci-static/resources/view/homeproxy/client.js:719 msgid "Override address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:352 +#: htdocs/luci-static/resources/view/homeproxy/client.js:375 msgid "Override destination" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:703 +#: htdocs/luci-static/resources/view/homeproxy/client.js:726 msgid "Override port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:353 +#: htdocs/luci-static/resources/view/homeproxy/client.js:376 msgid "Override the connection destination address with the sniffed domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:697 +#: htdocs/luci-static/resources/view/homeproxy/client.js:720 msgid "Override the connection destination address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:704 +#: htdocs/luci-static/resources/view/homeproxy/client.js:727 msgid "Override the connection destination port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:843 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1273 msgid "PUT" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:886 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1316 msgid "Packet encoding" msgstr "" @@ -1699,77 +1828,77 @@ msgstr "" msgid "Panic" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:464 +#: htdocs/luci-static/resources/view/homeproxy/node.js:894 #: htdocs/luci-static/resources/view/homeproxy/server.js:210 msgid "Password" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:967 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1346 -#: htdocs/luci-static/resources/view/homeproxy/node.js:836 -#: htdocs/luci-static/resources/view/homeproxy/node.js:869 +#: htdocs/luci-static/resources/view/homeproxy/client.js:990 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1369 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1266 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1299 #: htdocs/luci-static/resources/view/homeproxy/server.js:447 #: htdocs/luci-static/resources/view/homeproxy/server.js:477 msgid "Path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:911 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1341 msgid "Peer pubkic key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:329 +#: htdocs/luci-static/resources/view/homeproxy/client.js:352 msgid "" "Performance may degrade slightly, so it is not recommended to enable on when " "it is not needed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:936 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 msgid "Persistent keepalive interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:855 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1285 #: htdocs/luci-static/resources/view/homeproxy/server.js:464 msgid "Ping timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:630 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1060 msgid "Plugin" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:637 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1067 msgid "Plugin opts" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:848 -#: htdocs/luci-static/resources/view/homeproxy/client.js:962 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1286 -#: htdocs/luci-static/resources/view/homeproxy/node.js:453 +#: htdocs/luci-static/resources/view/homeproxy/client.js:871 +#: htdocs/luci-static/resources/view/homeproxy/client.js:985 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1309 +#: htdocs/luci-static/resources/view/homeproxy/node.js:883 msgid "Port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:276 +#: htdocs/luci-static/resources/view/homeproxy/client.js:299 msgid "Port %s alrealy exists!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:582 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/client.js:605 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1085 msgid "Port fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:536 +#: htdocs/luci-static/resources/view/homeproxy/node.js:966 msgid "Port hopping interval in seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:853 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1291 +#: htdocs/luci-static/resources/view/homeproxy/client.js:876 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1314 msgid "Port range" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1348 msgid "Pre-shared key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1150 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 msgid "Predefined" msgstr "" @@ -1781,106 +1910,110 @@ msgstr "" msgid "Prefer IPv6" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:678 -#: htdocs/luci-static/resources/view/homeproxy/node.js:903 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1108 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1333 msgid "Private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:683 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 msgid "Private key passphrase" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:583 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1063 +#: htdocs/luci-static/resources/view/homeproxy/node.js:484 +msgid "Probe failed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:606 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1086 msgid "Process fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:858 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1296 +#: htdocs/luci-static/resources/view/homeproxy/client.js:881 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1319 msgid "Process name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:862 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:885 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1323 msgid "Process path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:866 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1304 +#: htdocs/luci-static/resources/view/homeproxy/client.js:889 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1327 msgid "Process path (regex)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:615 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 -#: htdocs/luci-static/resources/view/homeproxy/node.js:543 -#: htdocs/luci-static/resources/view/homeproxy/node.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:638 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1127 +#: htdocs/luci-static/resources/view/homeproxy/node.js:973 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1381 #: htdocs/luci-static/resources/view/homeproxy/server.js:250 msgid "Protocol" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:767 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1197 msgid "Protocol parameter. Enable length block encryption." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:760 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1190 msgid "" "Protocol parameter. Will waste traffic randomly if enabled (enabled by " "default in v2ray and cannot be disabled)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1482 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1505 msgid "Proxy Domain List" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1437 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1466 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1460 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1489 msgid "Proxy IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1440 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1469 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1463 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1492 msgid "Proxy IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1443 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1466 msgid "Proxy MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1447 msgid "Proxy all except listed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1421 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1444 msgid "Proxy filter mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1423 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1446 msgid "Proxy listed only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:284 +#: htdocs/luci-static/resources/view/homeproxy/client.js:307 msgid "Proxy mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:497 +#: htdocs/luci-static/resources/view/homeproxy/node.js:927 msgid "Proxy protocol" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:621 -#: htdocs/luci-static/resources/view/homeproxy/client.js:953 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1109 -#: htdocs/luci-static/resources/view/homeproxy/node.js:712 -#: htdocs/luci-static/resources/view/homeproxy/node.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:644 +#: htdocs/luci-static/resources/view/homeproxy/client.js:976 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1132 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1209 #: htdocs/luci-static/resources/view/homeproxy/server.js:405 msgid "QUIC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:699 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1129 #: htdocs/luci-static/resources/view/homeproxy/server.js:355 msgid "QUIC congestion control algorithm." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:599 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1029 #: htdocs/luci-static/resources/view/homeproxy/server.js:307 msgid "QUIC connection receive window" msgstr "" @@ -1889,29 +2022,33 @@ msgstr "" msgid "QUIC maximum concurrent bidirectional streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:593 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1023 #: htdocs/luci-static/resources/view/homeproxy/server.js:300 msgid "QUIC stream receive window" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1095 +#: htdocs/luci-static/resources/view/homeproxy/client.js:209 +msgid "Quad9 Public DNS (DoH)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1118 msgid "Query type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1215 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1238 msgid "RCode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:622 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1110 +#: htdocs/luci-static/resources/view/homeproxy/client.js:645 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 msgid "RDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:917 +#: htdocs/luci-static/resources/view/homeproxy/client.js:940 msgid "RDRC timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1572 #: htdocs/luci-static/resources/view/homeproxy/server.js:709 msgid "REALITY" msgstr "" @@ -1920,42 +2057,50 @@ msgstr "" msgid "REALITY private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1147 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1577 #: htdocs/luci-static/resources/view/homeproxy/server.js:730 msgid "REALITY public key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1153 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1583 #: htdocs/luci-static/resources/view/homeproxy/server.js:734 msgid "REALITY short ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1785 +msgid "RP" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:486 +msgid "RPC failed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:63 #: htdocs/luci-static/resources/view/homeproxy/server.js:58 msgid "RUNNING" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:665 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1095 msgid "Random version will be used if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:488 +#: htdocs/luci-static/resources/view/homeproxy/client.js:511 msgid "Recursive outbound detected!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1013 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1036 msgid "Recursive resolver detected!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:285 +#: htdocs/luci-static/resources/view/homeproxy/client.js:308 msgid "Redirect TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:287 +#: htdocs/luci-static/resources/view/homeproxy/client.js:310 msgid "Redirect TCP + TProxy UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:289 +#: htdocs/luci-static/resources/view/homeproxy/client.js:312 msgid "Redirect TCP + Tun UDP" msgstr "" @@ -1967,40 +2112,40 @@ msgstr "" msgid "Region ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:672 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:695 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1172 msgid "Reject" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1336 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1359 msgid "Remote" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1434 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1874 msgid "Remove %s nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1864 msgid "Remove all nodes from subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1203 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1226 msgid "Reply with REFUSED" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:769 +#: htdocs/luci-static/resources/view/homeproxy/client.js:792 msgid "Reply with TCP RST / ICMP port unreachable" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:925 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1355 msgid "Reserved field bytes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:673 +#: htdocs/luci-static/resources/view/homeproxy/client.js:696 msgid "Resolve" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:803 msgid "Resolve strategy" msgstr "" @@ -2016,98 +2161,137 @@ msgstr "" msgid "Reuse listener address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:792 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1187 +#: htdocs/luci-static/resources/view/homeproxy/client.js:815 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1210 msgid "Rewrite TTL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:793 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1188 +#: htdocs/luci-static/resources/view/homeproxy/client.js:816 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 msgid "Rewrite TTL in DNS responses." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:670 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1147 +#: htdocs/luci-static/resources/view/homeproxy/client.js:693 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1170 msgid "Route" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:671 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1148 +#: htdocs/luci-static/resources/view/homeproxy/client.js:694 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1171 msgid "Route options" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:396 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Routing Nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:567 +#: htdocs/luci-static/resources/view/homeproxy/client.js:590 msgid "Routing Rules" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:120 +#: htdocs/luci-static/resources/view/homeproxy/client.js:145 msgid "Routing Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:251 +#: htdocs/luci-static/resources/view/homeproxy/client.js:274 msgid "Routing mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:428 msgid "Routing node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:287 msgid "Routing ports" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/client.js:599 msgid "Routing rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1311 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1334 msgid "Rule Set" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:646 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1119 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 +#: htdocs/luci-static/resources/view/homeproxy/client.js:669 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1343 msgid "Rule set" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:661 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1134 +msgid "Tag" +msgstr "" + +msgid "Used by sing-box rule sets. Please fill it in yourself." +msgstr "" + +msgid "valid tag" +msgstr "" + +msgid "valid duration" +msgstr "" + +msgid "valid cron expression" +msgstr "" + +msgid "Manual select" +msgstr "" + +msgid "Custom nodes" +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "Custom URL" +msgstr "" + +msgid "Policy nodes" +msgstr "" + +msgid "List of policy groups." +msgstr "" + +msgid "Subscription nodes" +msgstr "" + +msgid "List of nodes from subscriptions." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:684 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1157 msgid "Rule set IP CIDR as source IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1353 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1376 msgid "Rule set URL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:623 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1111 -#: htdocs/luci-static/resources/view/homeproxy/node.js:438 +#: htdocs/luci-static/resources/view/homeproxy/client.js:646 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1134 +#: htdocs/luci-static/resources/view/homeproxy/node.js:868 msgid "SSH" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:624 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 +#: htdocs/luci-static/resources/view/homeproxy/client.js:647 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 msgid "STUN" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1177 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1607 msgid "SUoT version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:632 +#: htdocs/luci-static/resources/view/homeproxy/client.js:655 msgid "Safari / Apple Network API" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:569 +#: htdocs/luci-static/resources/view/homeproxy/node.js:999 #: htdocs/luci-static/resources/view/homeproxy/server.js:290 msgid "Salamander" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:152 +#: htdocs/luci-static/resources/view/homeproxy/client.js:177 msgid "Same as main node" msgstr "" @@ -2116,15 +2300,15 @@ msgstr "" msgid "Save" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1397 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1837 msgid "Save current settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1394 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1834 msgid "Save subscriptions settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1155 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1178 #: htdocs/luci-static/resources/view/homeproxy/server.js:156 msgid "Server" msgstr "" @@ -2147,81 +2331,81 @@ msgstr "" msgid "Service Status" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1175 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1198 msgid "Set domain strategy for this query." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:436 +#: htdocs/luci-static/resources/view/homeproxy/node.js:866 msgid "ShadowTLS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1074 msgid "ShadowTLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:435 +#: htdocs/luci-static/resources/view/homeproxy/node.js:865 #: htdocs/luci-static/resources/view/homeproxy/server.js:184 msgid "Shadowsocks" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:628 +#: htdocs/luci-static/resources/view/homeproxy/client.js:651 msgid "Sniffed client type (QUIC client type or SSH client name)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:616 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1105 +#: htdocs/luci-static/resources/view/homeproxy/client.js:639 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1128 msgid "" "Sniffed protocol, see Sniff for details." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:867 #: htdocs/luci-static/resources/view/homeproxy/server.js:185 msgid "Socks" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:654 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1084 msgid "Socks version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:655 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1085 msgid "Socks4" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:656 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 msgid "Socks4A" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:657 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1087 msgid "Socks5" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:822 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1259 +#: htdocs/luci-static/resources/view/homeproxy/client.js:845 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1282 msgid "Source IP CIDR" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1342 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 msgid "Source file" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:838 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1276 +#: htdocs/luci-static/resources/view/homeproxy/client.js:861 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1299 msgid "Source port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:843 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1281 +#: htdocs/luci-static/resources/view/homeproxy/client.js:866 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1304 msgid "Source port range" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:750 +#: htdocs/luci-static/resources/view/homeproxy/client.js:773 msgid "" "Specifies DNS server tag to use instead of selecting through DNS routing." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:796 -#: htdocs/luci-static/resources/view/homeproxy/node.js:848 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1278 msgid "" "Specifies the period of time (in seconds) after which a health check will be " "performed using a ping frame if no frames have been received on the " @@ -2237,8 +2421,8 @@ msgid "" "GOAWAY frame. PING frames are not considered as activity." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:800 -#: htdocs/luci-static/resources/view/homeproxy/node.js:856 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1230 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1286 msgid "" "Specifies the timeout duration (in seconds) after sending a PING frame, " "within which a response must be received.
If a response to the PING " @@ -2246,40 +2430,34 @@ msgid "" "will be closed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:265 +#: htdocs/luci-static/resources/view/homeproxy/client.js:288 msgid "" "Specify target ports to be proxied. Multiple ports must be separated by " "commas." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:913 -msgid "Store RDRC" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:914 -msgid "" -"Store rejected DNS response cache.
The check results of Address " -"filter DNS rule items will be cached until expiration." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:557 +#: htdocs/luci-static/resources/view/homeproxy/node.js:987 #: htdocs/luci-static/resources/view/homeproxy/server.js:278 msgid "String" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1322 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1755 msgid "Sub (%s)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1349 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1789 msgid "Subscription URL-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1333 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1766 msgid "Subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1273 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +msgid "Success" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1706 msgid "Successfully imported %s nodes of total %s." msgstr "" @@ -2287,103 +2465,101 @@ msgstr "" msgid "Successfully updated." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1233 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1666 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1790 msgid "" "Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) " "online configuration delivery standard." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:180 +#: htdocs/luci-static/resources/view/homeproxy/client.js:205 msgid "" "Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:313 +#: htdocs/luci-static/resources/view/homeproxy/client.js:336 msgid "System" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:383 -#: htdocs/luci-static/resources/view/homeproxy/client.js:435 -#: htdocs/luci-static/resources/view/homeproxy/client.js:757 -#: htdocs/luci-static/resources/view/homeproxy/client.js:888 -#: htdocs/luci-static/resources/view/homeproxy/client.js:996 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/client.js:406 +#: htdocs/luci-static/resources/view/homeproxy/client.js:458 +#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:911 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1019 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1185 msgid "System DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:638 -#: htdocs/luci-static/resources/view/homeproxy/client.js:949 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1100 +#: htdocs/luci-static/resources/view/homeproxy/client.js:661 +#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1123 #: htdocs/luci-static/resources/view/homeproxy/server.js:867 msgid "TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1161 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1591 #: htdocs/luci-static/resources/view/homeproxy/server.js:845 msgid "TCP fast open" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:307 +#: htdocs/luci-static/resources/view/homeproxy/client.js:330 msgid "TCP/IP stack" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:308 +#: htdocs/luci-static/resources/view/homeproxy/client.js:331 msgid "TCP/IP stack." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:625 -#: htdocs/luci-static/resources/view/homeproxy/client.js:950 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1003 +#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:973 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1136 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1433 #: htdocs/luci-static/resources/view/homeproxy/server.js:532 msgid "TLS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1035 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1465 #: htdocs/luci-static/resources/view/homeproxy/server.js:565 msgid "TLS ALPN" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:980 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1030 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1003 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1460 #: htdocs/luci-static/resources/view/homeproxy/server.js:560 msgid "TLS SNI" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:735 +#: htdocs/luci-static/resources/view/homeproxy/client.js:758 msgid "TLS fragment" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:787 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1217 #: htdocs/luci-static/resources/view/homeproxy/server.js:413 msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:729 -#: htdocs/luci-static/resources/view/homeproxy/client.js:737 +#: htdocs/luci-static/resources/view/homeproxy/client.js:752 +#: htdocs/luci-static/resources/view/homeproxy/client.js:760 msgid "TLS record fragment" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 -msgid "" -"Tag of a another server to resolve the domain name in the address. Required " -"if address contains domain." +#: htdocs/luci-static/resources/view/homeproxy/client.js:1012 +msgid "Used to resolve DNS server addresses in domain form. IP addresses do not require it." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1028 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1051 msgid "Tag of an outbound for connecting to the dns server." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1376 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1399 msgid "Tag of the outbound to download rule set." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1156 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1179 msgid "Tag of the target dns server." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:702 msgid "Tag of the target outbound." msgstr "" @@ -2392,55 +2568,73 @@ msgid "" "Tell the client to use the BBR flow control algorithm instead of Hysteria CC." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:187 -#: htdocs/luci-static/resources/view/homeproxy/client.js:222 -msgid "Tencent Public DNS (119.29.29.29)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:245 +msgid "Tencent Public DNS (DoH)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:510 +#: htdocs/luci-static/resources/view/homeproxy/node.js:541 +#: htdocs/luci-static/resources/view/homeproxy/node.js:826 +msgid "Test" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1924 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1928 +msgid "Test Current List" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:533 msgid "Test URL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:138 -#: htdocs/luci-static/resources/view/homeproxy/client.js:167 -#: htdocs/luci-static/resources/view/homeproxy/client.js:530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:163 +#: htdocs/luci-static/resources/view/homeproxy/client.js:192 +#: htdocs/luci-static/resources/view/homeproxy/client.js:553 msgid "Test interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:538 +#: htdocs/luci-static/resources/view/homeproxy/client.js:561 msgid "Test interval must be less or equal than idle timeout." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:144 -#: htdocs/luci-static/resources/view/homeproxy/client.js:173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:546 +#: htdocs/luci-static/resources/view/homeproxy/client.js:169 +#: htdocs/luci-static/resources/view/homeproxy/client.js:198 +#: htdocs/luci-static/resources/view/homeproxy/client.js:569 msgid "Test tolerance" msgstr "" +#: htdocs/luci-static/resources/view/homeproxy/node.js:498 +#: htdocs/luci-static/resources/view/homeproxy/node.js:541 +msgid "Testing" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1917 +msgid "Testing Current List..." +msgstr "" + #: htdocs/luci-static/resources/view/homeproxy/server.js:628 msgid "The ACME CA provider to use." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:878 +#: htdocs/luci-static/resources/view/homeproxy/client.js:901 msgid "The DNS strategy for resolving the domain name in the address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:600 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1030 #: htdocs/luci-static/resources/view/homeproxy/server.js:308 msgid "The QUIC connection-level flow control window for receiving data." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:594 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1024 #: htdocs/luci-static/resources/view/homeproxy/server.js:301 msgid "The QUIC stream-level flow control window for receiving data." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:511 +#: htdocs/luci-static/resources/view/homeproxy/client.js:534 msgid "The URL to test." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:958 -msgid "The address of the dns server." +#: htdocs/luci-static/resources/view/homeproxy/client.js:981 +msgid "Full URL, e.g. https://dns.alidns.com/dns-query" msgstr "" #: htdocs/luci-static/resources/view/homeproxy/server.js:679 @@ -2455,38 +2649,21 @@ msgid "" "forward 443 to this port for challenge to succeed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:596 -msgid "" -"The default rule uses the following matching logic:
(domain || " -"domain_suffix || domain_keyword || domain_regex || ip_cidr || " -"ip_is_private) &&
(port || port_range) &&
(source_ip_cidr || source_ip_is_private) &&
(source_port || source_port_range) &&
other fields.
Additionally, included rule sets can be considered merged rather " -"than as a single rule sub-item." +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "In default mode, rule fields are matched by category. Any condition in the same category can match, while different categories must match at the same time. Rule sets are merged into the rule for matching and are not treated as separate sub-rules." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1076 -msgid "" -"The default rule uses the following matching logic:
(domain || " -"domain_suffix || domain_keyword || domain_regex) &&
(port " -"|| port_range) &&
(source_ip_cidr || source_ip_is_private) &&
(source_port || source_port_range) &&
other fields.
Additionally, included rule sets can be " -"considered merged rather than as a single rule sub-item." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:218 +#: htdocs/luci-static/resources/view/homeproxy/client.js:242 msgid "" "The dns server for resolving China domains. Support UDP, TCP, DoH, DoQ, DoT." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:447 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1021 +#: htdocs/luci-static/resources/view/homeproxy/client.js:470 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1044 msgid "The domain strategy for resolving the domain name in the address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1065 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1495 #: htdocs/luci-static/resources/view/homeproxy/server.js:587 msgid "" "The elliptic curves that will be used in an ECDHE handshake, in preference " @@ -2499,17 +2676,17 @@ msgid "" "account." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:743 +#: htdocs/luci-static/resources/view/homeproxy/client.js:766 msgid "" "The fallback value in milliseconds used when TLS segmentation cannot " "automatically determine the wait time." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:554 +#: htdocs/luci-static/resources/view/homeproxy/client.js:577 msgid "The idle timeout in seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1057 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1487 #: htdocs/luci-static/resources/view/homeproxy/server.js:579 msgid "The maximum TLS version that is acceptable." msgstr "" @@ -2524,32 +2701,28 @@ msgstr "" msgid "The maximum time difference between the server and the client." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1049 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1479 #: htdocs/luci-static/resources/view/homeproxy/server.js:571 msgid "The minimum TLS version that is acceptable." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:102 +#: htdocs/luci-static/resources/view/homeproxy/client.js:113 #: htdocs/luci-static/resources/view/homeproxy/server.js:130 -msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64." +msgid "OpenWrt-designed Sing-box proxy management platform. It is recommended to disable Dnsmasq DNS redirection." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:477 #: htdocs/luci-static/resources/view/homeproxy/server.js:875 msgid "The network interface to bind to." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:968 -msgid "The path of the DNS server." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1099 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1529 msgid "" "The path to the ECH config, in PEM format. If empty, load from DNS will be " "attempted." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1078 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1508 msgid "The path to the server certificate, in PEM format." msgstr "" @@ -2557,11 +2730,11 @@ msgstr "" msgid "The port must be unique." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:963 -msgid "The port of the DNS server." +#: htdocs/luci-static/resources/view/homeproxy/client.js:986 +msgid "Leave empty to use the default port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1216 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1239 msgid "The response code." msgstr "" @@ -2573,25 +2746,25 @@ msgstr "" msgid "The server public key, in PEM format." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:461 +#: htdocs/luci-static/resources/view/homeproxy/client.js:484 msgid "" "The tag of the upstream outbound.
Other dial fields will be ignored when " "enabled." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:139 -#: htdocs/luci-static/resources/view/homeproxy/client.js:168 -#: htdocs/luci-static/resources/view/homeproxy/client.js:531 +#: htdocs/luci-static/resources/view/homeproxy/client.js:164 +#: htdocs/luci-static/resources/view/homeproxy/client.js:193 +#: htdocs/luci-static/resources/view/homeproxy/client.js:554 msgid "The test interval in seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:145 -#: htdocs/luci-static/resources/view/homeproxy/client.js:174 -#: htdocs/luci-static/resources/view/homeproxy/client.js:547 +#: htdocs/luci-static/resources/view/homeproxy/client.js:170 +#: htdocs/luci-static/resources/view/homeproxy/client.js:199 +#: htdocs/luci-static/resources/view/homeproxy/client.js:570 msgid "The test tolerance in milliseconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:807 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1237 #: htdocs/luci-static/resources/view/homeproxy/server.js:465 msgid "" "The timeout (in seconds) that after performing a keepalive check, the client " @@ -2599,31 +2772,34 @@ msgid "" "closed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1043 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1473 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1825 msgid "" "This is DANGEROUS, your traffic is almost like " "PLAIN TEXT! Use at your own risk!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:717 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1147 msgid "" "This is the TUIC port of the UDP over TCP protocol, designed to provide a " "QUIC stream based UDP relay mode that TUIC does not provide." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:188 -#: htdocs/luci-static/resources/view/homeproxy/client.js:223 -msgid "ThreatBook Public DNS (117.50.10.10)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:244 +msgid "ThreatBook Public DNS (DoH)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:723 +#: htdocs/luci-static/resources/view/homeproxy/node.js:502 +msgid "Timeout" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:746 msgid "" "Timeout for UDP connections.
Setting a larger value than the UDP timeout " "in inbounds will have no effect." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:918 +#: htdocs/luci-static/resources/view/homeproxy/client.js:941 msgid "" "Timeout of rejected DNS response cache in seconds. 604800 (7d) " "is used by default." @@ -2635,7 +2811,7 @@ msgid "" "code>." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:292 +#: htdocs/luci-static/resources/view/homeproxy/client.js:315 msgid "" "To enable Tun support, you need to install ip-full and " "kmod-tun" @@ -2645,82 +2821,98 @@ msgstr "" msgid "Trace" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:773 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1203 #: htdocs/luci-static/resources/view/homeproxy/server.js:399 msgid "Transport" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:439 +#: htdocs/luci-static/resources/view/homeproxy/node.js:869 #: htdocs/luci-static/resources/view/homeproxy/server.js:186 msgid "Trojan" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:441 +#: htdocs/luci-static/resources/view/homeproxy/node.js:871 #: htdocs/luci-static/resources/view/homeproxy/server.js:188 msgid "Tuic" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:290 +#: htdocs/luci-static/resources/view/homeproxy/client.js:313 msgid "Tun TCP/UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:947 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1334 -#: htdocs/luci-static/resources/view/homeproxy/node.js:427 +#: htdocs/luci-static/resources/view/homeproxy/client.js:970 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 +#: htdocs/luci-static/resources/view/homeproxy/node.js:857 #: htdocs/luci-static/resources/view/homeproxy/server.js:175 msgid "Type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:639 -#: htdocs/luci-static/resources/view/homeproxy/client.js:948 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1101 +msgid "Type error" +msgstr "" + +msgid "TypeError" +msgstr "" + +msgid "Cannot convert null to object." +msgstr "" + +msgid "can't convert null to object" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:662 +#: htdocs/luci-static/resources/view/homeproxy/client.js:971 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1124 #: htdocs/luci-static/resources/view/homeproxy/server.js:868 msgid "UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1167 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1597 #: htdocs/luci-static/resources/view/homeproxy/server.js:854 msgid "UDP Fragment" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:358 #: htdocs/luci-static/resources/view/homeproxy/server.js:859 msgid "UDP NAT expiration time" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1171 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1601 msgid "UDP over TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:716 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1146 msgid "UDP over stream" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:709 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1139 msgid "UDP packet relay mode." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:708 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1138 msgid "UDP relay mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:722 +#: htdocs/luci-static/resources/view/homeproxy/client.js:745 msgid "UDP timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:124 -#: htdocs/luci-static/resources/view/homeproxy/client.js:153 -#: htdocs/luci-static/resources/view/homeproxy/client.js:421 +#: htdocs/luci-static/resources/view/homeproxy/client.js:149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:444 msgid "URLTest" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:131 -#: htdocs/luci-static/resources/view/homeproxy/client.js:160 -#: htdocs/luci-static/resources/view/homeproxy/client.js:496 +#: htdocs/luci-static/resources/view/homeproxy/client.js:156 +#: htdocs/luci-static/resources/view/homeproxy/client.js:185 +#: htdocs/luci-static/resources/view/homeproxy/client.js:519 msgid "URLTest nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:690 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 +msgid "URLTest: %s" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1120 #: htdocs/luci-static/resources/view/homeproxy/server.js:346 msgid "UUID" msgstr "" @@ -2733,11 +2925,11 @@ msgstr "" msgid "Unknown error: %s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1565 msgid "Unsupported fingerprint!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1409 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1849 msgid "Update %s subscriptions" msgstr "" @@ -2745,45 +2937,77 @@ msgstr "" msgid "Update failed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1392 -msgid "Update interval" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:1393 -msgid "Update interval of rule set." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1404 -msgid "Update nodes from subscriptions" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1346 -msgid "Update subscriptions via proxy." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1339 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1415 msgid "Update time" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1345 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1416 +msgid "Update time (daily)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1417 +msgid "Update time (weekly)" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1418 +msgid "Every day" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1419 +msgid "Every Monday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1420 +msgid "Every Tuesday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1421 +msgid "Every Wednesday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 +msgid "Every Thursday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1423 +msgid "Every Friday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1424 +msgid "Every Saturday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1425 +msgid "Every Sunday" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1844 +msgid "Update nodes from subscriptions" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1779 +msgid "Update subscriptions via proxy." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1778 msgid "Update via proxy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1104 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1534 msgid "Upload ECH config" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:995 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1425 #: htdocs/luci-static/resources/view/homeproxy/server.js:523 msgid "Upload bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:996 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1426 #: htdocs/luci-static/resources/view/homeproxy/server.js:524 msgid "Upload bandwidth in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1085 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1515 #: htdocs/luci-static/resources/view/homeproxy/server.js:767 msgid "Upload certificate" msgstr "" @@ -2792,8 +3016,8 @@ msgstr "" msgid "Upload key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1088 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1107 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1518 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1537 #: htdocs/luci-static/resources/view/homeproxy/server.js:770 #: htdocs/luci-static/resources/view/homeproxy/server.js:789 msgid "Upload..." @@ -2803,47 +3027,47 @@ msgstr "" msgid "Use ACME TLS certificate issuer." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1031 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1461 #: htdocs/luci-static/resources/view/homeproxy/server.js:561 msgid "" "Used to verify the hostname on the returned certificates unless insecure is " "given." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:981 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 msgid "Used to verify the hostname on the returned certificates." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:642 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:665 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 msgid "User" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1379 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1819 msgid "User-Agent" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:458 +#: htdocs/luci-static/resources/view/homeproxy/node.js:888 #: htdocs/luci-static/resources/view/homeproxy/server.js:203 msgid "Username" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:444 +#: htdocs/luci-static/resources/view/homeproxy/node.js:874 #: htdocs/luci-static/resources/view/homeproxy/server.js:189 msgid "VLESS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:875 #: htdocs/luci-static/resources/view/homeproxy/server.js:190 msgid "VMess" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:181 -#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:243 msgid "WAN DNS (read from interface)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1464 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1487 msgid "WAN IP Policy" msgstr "" @@ -2851,37 +3075,37 @@ msgstr "" msgid "Warn" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:780 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1210 #: htdocs/luci-static/resources/view/homeproxy/server.js:406 msgid "WebSocket" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1370 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1810 msgid "Whitelist mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:443 +#: htdocs/luci-static/resources/view/homeproxy/node.js:873 msgid "WireGuard" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:912 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1342 msgid "WireGuard peer public key." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:919 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1349 msgid "WireGuard pre-shared key." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:904 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1334 msgid "WireGuard requires base64-encoded private keys." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:498 +#: htdocs/luci-static/resources/view/homeproxy/node.js:928 msgid "Write proxy protocol in the connection header." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:889 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1392 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1319 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1832 msgid "Xudp (Xray-core)" msgstr "" @@ -2893,17 +3117,17 @@ msgstr "" msgid "ZeroSSL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1520 #: htdocs/luci-static/resources/view/homeproxy/server.js:772 msgid "certificate" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:716 +#: htdocs/luci-static/resources/view/homeproxy/client.js:739 msgid "connect UDP connections" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1050 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1058 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1480 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1488 #: htdocs/luci-static/resources/view/homeproxy/server.js:572 #: htdocs/luci-static/resources/view/homeproxy/server.js:580 msgid "default" @@ -2913,44 +3137,52 @@ msgstr "" msgid "failed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1206 #: htdocs/luci-static/resources/view/homeproxy/server.js:402 msgid "gRPC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:818 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1248 msgid "gRPC permit without stream" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:813 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1243 #: htdocs/luci-static/resources/view/homeproxy/server.js:430 msgid "gRPC service name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:311 +#: htdocs/luci-static/resources/view/homeproxy/client.js:334 msgid "gVisor" msgstr "" #: htdocs/luci-static/resources/homeproxy.js:306 #: htdocs/luci-static/resources/homeproxy.js:326 -#: htdocs/luci-static/resources/view/homeproxy/client.js:195 -#: htdocs/luci-static/resources/view/homeproxy/client.js:230 -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 -#: htdocs/luci-static/resources/view/homeproxy/node.js:488 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:527 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1380 +#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1561 #: htdocs/luci-static/resources/view/homeproxy/server.js:235 msgid "non-empty value" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:631 -#: htdocs/luci-static/resources/view/homeproxy/node.js:887 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1390 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1919 +msgid "The default rule set directory is /etc/homeproxy/ruleset/." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1975 +msgid "absolute path" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1061 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1317 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1830 msgid "none" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:888 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1391 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1318 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1831 msgid "packet addr (v2ray-core v5+)" msgstr "" @@ -2962,7 +3194,7 @@ msgstr "" msgid "private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:631 +#: htdocs/luci-static/resources/view/homeproxy/client.js:654 msgid "quic-go / uquic chrome" msgstr "" @@ -2974,11 +3206,11 @@ msgstr "" msgid "sing-box server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1543 msgid "uTLS fingerprint" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1114 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1544 msgid "" "uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting " "resistance." @@ -2989,7 +3221,7 @@ msgid "unchecked" msgstr "" #: htdocs/luci-static/resources/homeproxy.js:237 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1302 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1735 msgid "unique UCI identifier" msgstr "" @@ -2997,35 +3229,35 @@ msgstr "" msgid "unique value" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:500 -#: htdocs/luci-static/resources/view/homeproxy/node.js:645 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 +#: htdocs/luci-static/resources/view/homeproxy/node.js:930 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1075 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1608 msgid "v1" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:501 -#: htdocs/luci-static/resources/view/homeproxy/node.js:646 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1179 +#: htdocs/luci-static/resources/view/homeproxy/node.js:931 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1076 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1609 msgid "v2" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:647 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1077 msgid "v3" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:207 -#: htdocs/luci-static/resources/view/homeproxy/client.js:211 -#: htdocs/luci-static/resources/view/homeproxy/client.js:241 -#: htdocs/luci-static/resources/view/homeproxy/client.js:245 +#: htdocs/luci-static/resources/view/homeproxy/client.js:231 +#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:268 msgid "valid DNS server address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:518 -#: htdocs/luci-static/resources/view/homeproxy/client.js:521 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1362 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1359 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1388 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1796 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1799 msgid "valid URL" msgstr "" @@ -3033,8 +3265,8 @@ msgstr "" msgid "valid base64 key with %d characters" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1539 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1562 msgid "valid hostname" msgstr "" @@ -3042,10 +3274,176 @@ msgstr "" msgid "valid port range (port1:port2)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:274 +#: htdocs/luci-static/resources/view/homeproxy/client.js:297 msgid "valid port value" msgstr "" #: htdocs/luci-static/resources/homeproxy.js:328 msgid "valid uuid" msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Panel Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI path" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI download URL" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI download detour" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "API listen" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Use 0.0.0.0:port to open the panel from your browser." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "API password" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Default mode" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable NTP" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP server address" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP server port" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP time synchronization interval" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP service always uses direct connection and mainly provides accurate time for sing-box features that depend on it, such as TLS certificate verification, VMess, Reality/uTLS and other time-sensitive connection scenarios." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Time format examples: 1m = 1 minute, 1h = 1 hour, 1d = 1 day." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persistent Cache Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable cache" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable cache file" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache file path" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persistent cache file path" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache RDRC" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persist RDRC cache" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Status" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Plugin Version" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Core Version" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Core Status" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Running" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Running: %s" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Not Running" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Reload Service" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Restart Service" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Update Panel" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Update panel failed." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Clash API unavailable." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Open Panel" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Upload Panel ZIP" +msgstr "" + +msgid "Baidu Connection" +msgstr "" + +msgid "Google Connection" +msgstr "" + +msgid "GitHub Connection" +msgstr "" + +msgid "YouTube Connection" +msgstr "" + +msgid "Click to check" +msgstr "" + +msgid "Checking..." +msgstr "" diff --git a/luci-app-homeproxy/po/zh_Hans/homeproxy.po b/luci-app-homeproxy/po/zh_Hans/homeproxy.po index 68d3e066a..26122baaf 100644 --- a/luci-app-homeproxy/po/zh_Hans/homeproxy.po +++ b/luci-app-homeproxy/po/zh_Hans/homeproxy.po @@ -8,11 +8,19 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 8bit\n" +#: htdocs/luci-static/resources/homeproxy.js:321 +msgid "Close" +msgstr "关闭" + #: htdocs/luci-static/resources/view/homeproxy/status.js:206 msgid "%s log" msgstr "%s 日志" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1456 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +msgid "%s ms" +msgstr "%s 毫秒" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1896 msgid "%s nodes removed" msgstr "移除了 %s 个节点" @@ -20,12 +28,12 @@ msgstr "移除了 %s 个节点" msgid "/etc/homeproxy/certs/..., /etc/acme/..., /etc/ssl/..." msgstr "/etc/homeproxy/certs/...,/etc/acme/...,/etc/ssl/..." -#: htdocs/luci-static/resources/view/homeproxy/client.js:609 +#: htdocs/luci-static/resources/view/homeproxy/client.js:632 msgid "4 or 6. Not limited if empty." msgstr "4 或 6。留空不限制。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:775 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1210 +#: htdocs/luci-static/resources/view/homeproxy/client.js:798 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1233 msgid "" "%s will be temporarily overwritten to %s after 50 " "triggers in 30s if not enabled." @@ -33,8 +41,8 @@ msgstr "" "%s 在未启用的情况下,50 次触发后会在 30 秒内临时覆盖为 " "%s。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1105 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1516 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1535 #: htdocs/luci-static/resources/view/homeproxy/server.js:768 #: htdocs/luci-static/resources/view/homeproxy/server.js:787 msgid "Save your configuration before uploading files!" @@ -44,15 +52,15 @@ msgstr "上传文件前请先保存配置!" msgid "API token" msgstr "API 令牌" -#: htdocs/luci-static/resources/view/homeproxy/node.js:670 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1100 msgid "Accept any if empty." msgstr "留空则不校验。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 msgid "Accept empty query response" msgstr "接受空查询响应" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1399 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 msgid "Access Control" msgstr "访问控制" @@ -64,32 +72,36 @@ msgstr "访问密钥 ID" msgid "Access key secret" msgstr "访问密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:669 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1146 +#: htdocs/luci-static/resources/view/homeproxy/client.js:692 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1169 msgid "Action" msgstr "操作" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:210 +msgid "AdGuard Public DNS (DoH)" +msgstr "AdGuard 公共DNS (DoH)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1079 msgid "Add a DNS rule" msgstr "新增 DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "Add a DNS server" msgstr "新增 DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 msgid "Add a node" msgstr "新增节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:428 msgid "Add a routing node" -msgstr "新增路由节点" +msgstr "新增策略组" -#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/client.js:599 msgid "Add a routing rule" msgstr "新增路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1343 msgid "Add a rule set" msgstr "新增规则集" @@ -97,33 +109,32 @@ msgstr "新增规则集" msgid "Add a server" msgstr "新增服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:975 +#: htdocs/luci-static/resources/view/homeproxy/client.js:998 msgid "Additional headers to be sent to the DNS server." msgstr "发送到 DNS 服务器的附加标头。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:957 -#: htdocs/luci-static/resources/view/homeproxy/node.js:448 +#: htdocs/luci-static/resources/view/homeproxy/client.js:980 +#: htdocs/luci-static/resources/view/homeproxy/node.js:878 msgid "Address" msgstr "地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:988 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1011 msgid "Address resolver" msgstr "地址解析器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1020 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1043 msgid "Address strategy" msgstr "地址解析策略" #: htdocs/luci-static/resources/view/homeproxy/server.js:640 -msgid "Alibaba Cloud DNS" -msgstr "阿里云 DNS" +msgid "AliYun" +msgstr "AliYun" -#: htdocs/luci-static/resources/view/homeproxy/client.js:186 -#: htdocs/luci-static/resources/view/homeproxy/client.js:220 -msgid "Aliyun Public DNS (223.5.5.5)" -msgstr "阿里云公共 DNS(223.5.5.5)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +msgid "AliYun Public DNS (DoH)" +msgstr "阿里 公共DNS (DoH)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:266 +#: htdocs/luci-static/resources/view/homeproxy/client.js:289 msgid "All ports" msgstr "所有端口" @@ -131,20 +142,20 @@ msgstr "所有端口" msgid "Allow access from the Internet." msgstr "允许来自互联网的访问。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1040 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1382 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1470 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1822 msgid "Allow insecure" msgstr "允许不安全连接" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1041 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1471 msgid "Allow insecure connection at TLS client." msgstr "允许 TLS 客户端侧的不安全连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1383 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1823 msgid "Allow insecure connection by default when add nodes from subscriptions." msgstr "从订阅获取节点时,默认允许不安全连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:874 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1304 #: htdocs/luci-static/resources/view/homeproxy/server.js:482 msgid "Allowed payload size is in the request." msgstr "请求中允许的载荷大小。" @@ -157,7 +168,7 @@ msgstr "已是最新版本。" msgid "Already in updating." msgstr "已在更新中。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1172 #: htdocs/luci-static/resources/view/homeproxy/server.js:391 msgid "Alter ID" msgstr "额外 ID" @@ -170,15 +181,15 @@ msgstr "替代 HTTP 端口" msgid "Alternative TLS port" msgstr "替代 HTTPS 端口" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1419 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1859 msgid "An error occurred during updating subscriptions: %s" msgstr "更新订阅时发生错误:%s" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1250 msgid "Answer" msgstr "回答" -#: htdocs/luci-static/resources/view/homeproxy/node.js:429 +#: htdocs/luci-static/resources/view/homeproxy/node.js:859 #: htdocs/luci-static/resources/view/homeproxy/server.js:176 msgid "AnyTLS" msgstr "AnyTLS" @@ -187,9 +198,9 @@ msgstr "AnyTLS" msgid "AnyTLS padding scheme in array." msgstr "AnyTLS 填充方案。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:799 -#: htdocs/luci-static/resources/view/homeproxy/client.js:909 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1195 +#: htdocs/luci-static/resources/view/homeproxy/client.js:822 +#: htdocs/luci-static/resources/view/homeproxy/client.js:932 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1218 msgid "" "Append a edns0-subnet OPT extra record with the specified IP " "prefix to every query by default.
If value is an IP address instead of " @@ -198,20 +209,20 @@ msgstr "" "将带有指定 IP 前缀的 edns0-subnet OPT 记录附加到每个查询。如果值" "是 IP 地址而不是前缀,则会自动添加 /32 或 /128。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1072 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1502 msgid "Append self-signed certificate" msgstr "追加自签名证书" -#: htdocs/luci-static/resources/view/homeproxy/node.js:407 +#: htdocs/luci-static/resources/view/homeproxy/node.js:802 msgid "Applied" msgstr "已应用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:400 -#: htdocs/luci-static/resources/view/homeproxy/node.js:410 +#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/node.js:805 msgid "Apply" msgstr "应用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:18 +#: htdocs/luci-static/resources/view/homeproxy/node.js:26 msgid "Are you sure to allow insecure?" msgstr "确定要允许不安全连接吗?" @@ -219,90 +230,98 @@ msgstr "确定要允许不安全连接吗?" msgid "Auth timeout" msgstr "认证超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:766 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1196 msgid "Authenticated length" msgstr "认证长度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:991 #: htdocs/luci-static/resources/view/homeproxy/server.js:282 msgid "Authentication payload" msgstr "认证载荷" -#: htdocs/luci-static/resources/view/homeproxy/node.js:554 +#: htdocs/luci-static/resources/view/homeproxy/node.js:984 #: htdocs/luci-static/resources/view/homeproxy/server.js:275 msgid "Authentication type" msgstr "认证类型" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:703 +msgid "Auto select" +msgstr "自动选择" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1768 msgid "Auto update" msgstr "自动更新" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1336 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1769 msgid "Auto update subscriptions and geodata." msgstr "自动更新订阅和地理数据。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:702 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1132 msgid "BBR" msgstr "BBR" +#: htdocs/luci-static/resources/view/homeproxy/node.js:472 +msgid "Backend execution failed" +msgstr "后端执行失败" + #: htdocs/luci-static/resources/view/homeproxy/status.js:235 msgid "BaiDu" msgstr "百度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:556 +#: htdocs/luci-static/resources/view/homeproxy/node.js:986 #: htdocs/luci-static/resources/view/homeproxy/server.js:277 msgid "Base64" msgstr "Base64" -#: htdocs/luci-static/resources/view/homeproxy/client.js:323 +#: htdocs/luci-static/resources/view/homeproxy/client.js:346 msgid "Based on google/gvisor." msgstr "基于 google/gvisor。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1341 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1364 msgid "Binary file" msgstr "二进制文件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:453 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1412 +#: htdocs/luci-static/resources/view/homeproxy/client.js:476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1435 #: htdocs/luci-static/resources/view/homeproxy/server.js:874 msgid "Bind interface" msgstr "绑定接口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1413 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1436 msgid "" "Bind outbound traffic to specific interface. Leave empty to auto detect." msgstr "绑定出站流量至指定端口。留空自动检测。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:617 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 +#: htdocs/luci-static/resources/view/homeproxy/client.js:640 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1129 msgid "BitTorrent" msgstr "BitTorrent" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1369 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1809 msgid "Blacklist mode" msgstr "黑名单模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:365 +#: htdocs/luci-static/resources/view/homeproxy/client.js:388 msgid "Block" msgstr "封锁" -#: htdocs/luci-static/resources/view/homeproxy/client.js:612 -#: htdocs/luci-static/resources/view/homeproxy/client.js:640 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1092 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1102 +#: htdocs/luci-static/resources/view/homeproxy/client.js:635 +#: htdocs/luci-static/resources/view/homeproxy/client.js:663 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 #: htdocs/luci-static/resources/view/homeproxy/server.js:869 msgid "Both" msgstr "全部" -#: htdocs/luci-static/resources/view/homeproxy/client.js:343 +#: htdocs/luci-static/resources/view/homeproxy/client.js:366 msgid "Bypass CN traffic" msgstr "绕过中国流量" -#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:276 msgid "Bypass mainland China" msgstr "大陆白名单" -#: htdocs/luci-static/resources/view/homeproxy/client.js:344 +#: htdocs/luci-static/resources/view/homeproxy/client.js:367 msgid "Bypass mainland China traffic via firewall rules by default." msgstr "默认使用防火墙规则绕过中国大陆流量。" @@ -310,19 +329,15 @@ msgstr "默认使用防火墙规则绕过中国大陆流量。" msgid "CA provider" msgstr "CA 颁发机构" -#: htdocs/luci-static/resources/view/homeproxy/client.js:221 -msgid "CNNIC Public DNS (210.2.4.8)" -msgstr "CNNIC 公共 DNS(210.2.4.8)" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:700 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1130 msgid "CUBIC" msgstr "CUBIC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1239 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1672 msgid "Cancel" -msgstr "取消" +msgstr "关闭" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1077 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1507 #: htdocs/luci-static/resources/view/homeproxy/server.js:756 msgid "Certificate path" msgstr "证书路径" @@ -335,7 +350,7 @@ msgstr "检查" msgid "Check update" msgstr "检查更新" -#: htdocs/luci-static/resources/view/homeproxy/client.js:217 +#: htdocs/luci-static/resources/view/homeproxy/client.js:241 msgid "China DNS server" msgstr "国内 DNS 服务器" @@ -351,24 +366,28 @@ msgstr "国内 IPv6 库版本" msgid "China list version" msgstr "国内域名列表版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:629 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1783 +msgid "Choose which method node latency tests use." +msgstr "选择节点延迟测试使用的方法。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:652 msgid "Chromium / Cronet" msgstr "Chromium / Cronet" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1064 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1494 #: htdocs/luci-static/resources/view/homeproxy/server.js:586 msgid "Cipher suites" msgstr "密码套件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:183 -msgid "Cisco Public DNS (208.67.222.222)" -msgstr "思科公共 DNS(208.67.222.222)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:212 +msgid "Cisco Public DNS (DoH)" +msgstr "Cisco 公共DNS (DoH)" #: htdocs/luci-static/resources/view/homeproxy/status.js:214 msgid "Clean log" msgstr "清空日志" -#: htdocs/luci-static/resources/view/homeproxy/client.js:627 +#: htdocs/luci-static/resources/view/homeproxy/client.js:650 msgid "Client" msgstr "客户端" @@ -376,29 +395,29 @@ msgstr "客户端" msgid "Client Settings" msgstr "客户端设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:664 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1094 msgid "Client version" msgstr "客户端版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:182 -msgid "CloudFlare Public DNS (1.1.1.1)" -msgstr "CloudFlare 公共 DNS(1.1.1.1)" - #: htdocs/luci-static/resources/view/homeproxy/server.js:641 -msgid "Cloudflare" -msgstr "Cloudflare" +msgid "CloudFlare" +msgstr "CloudFlare" -#: htdocs/luci-static/resources/view/homeproxy/client.js:114 +#: htdocs/luci-static/resources/view/homeproxy/client.js:207 +msgid "CloudFlare Public DNS (DoH)" +msgstr "CloudFlare 公共DNS (DoH)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:139 #: htdocs/luci-static/resources/view/homeproxy/server.js:142 #: htdocs/luci-static/resources/view/homeproxy/status.js:176 msgid "Collecting data..." msgstr "正在收集数据中..." -#: htdocs/luci-static/resources/view/homeproxy/client.js:267 +#: htdocs/luci-static/resources/view/homeproxy/client.js:290 msgid "Common ports only (bypass P2P traffic)" msgstr "仅常用端口(绕过 P2P 流量)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:698 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1128 #: htdocs/luci-static/resources/view/homeproxy/server.js:354 msgid "Congestion control algorithm" msgstr "拥塞控制算法" @@ -407,23 +426,27 @@ msgstr "拥塞控制算法" msgid "Connection check" msgstr "连接检查" -#: htdocs/luci-static/resources/view/homeproxy/client.js:255 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1772 +msgid "Cron expression" +msgstr "定时任务表达式" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:278 msgid "Custom routing" msgstr "自定义路由" -#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:641 msgid "DNS" msgstr "DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1047 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1070 msgid "DNS Rules" msgstr "DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:924 +#: htdocs/luci-static/resources/view/homeproxy/client.js:947 msgid "DNS Servers" msgstr "DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:895 msgid "DNS Settings" msgstr "DNS 设置" @@ -431,22 +454,26 @@ msgstr "DNS 设置" msgid "DNS provider" msgstr "DNS 提供商" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1079 msgid "DNS rule" msgstr "DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:179 -#: htdocs/luci-static/resources/view/homeproxy/client.js:749 -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:204 +#: htdocs/luci-static/resources/view/homeproxy/client.js:772 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "DNS server" msgstr "DNS 服务器" +#: htdocs/luci-static/resources/view/homeproxy/client.js:211 +msgid "DNS.SB Public DNS (DoH)" +msgstr "DNS.SB 公共DNS (DoH)" + #: htdocs/luci-static/resources/view/homeproxy/server.js:635 msgid "DNS01 challenge" msgstr "DNS01 验证" -#: htdocs/luci-static/resources/view/homeproxy/client.js:619 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1107 +#: htdocs/luci-static/resources/view/homeproxy/client.js:642 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 msgid "DTLS" msgstr "DTLS" @@ -455,49 +482,56 @@ msgid "Debug" msgstr "调试" #: htdocs/luci-static/resources/homeproxy.js:17 -#: htdocs/luci-static/resources/view/homeproxy/client.js:433 -#: htdocs/luci-static/resources/view/homeproxy/client.js:603 -#: htdocs/luci-static/resources/view/homeproxy/client.js:755 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1083 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1381 -#: htdocs/luci-static/resources/view/homeproxy/node.js:710 +#: htdocs/luci-static/resources/view/homeproxy/client.js:456 +#: htdocs/luci-static/resources/view/homeproxy/client.js:626 +#: htdocs/luci-static/resources/view/homeproxy/client.js:778 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1404 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1140 msgid "Default" msgstr "默认" -#: htdocs/luci-static/resources/view/homeproxy/client.js:382 -#: htdocs/luci-static/resources/view/homeproxy/client.js:434 -#: htdocs/luci-static/resources/view/homeproxy/client.js:756 -#: htdocs/luci-static/resources/view/homeproxy/client.js:887 -#: htdocs/luci-static/resources/view/homeproxy/client.js:995 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 +msgid "Default node" +msgstr "默认节点" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:457 +#: htdocs/luci-static/resources/view/homeproxy/client.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:910 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1018 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1184 msgid "Default DNS (issued by WAN)" msgstr "默认 DNS(由 WAN 下发)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:905 msgid "Default DNS server" msgstr "默认 DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:377 -msgid "Default DNS server for resolving domain name in the server address." -msgstr "用于解析服务器地址中的域名的默认 DNS 服务器。" +#: htdocs/luci-static/resources/view/homeproxy/client.js:1332 +msgid "Daily domain name resolution for clients. Choose an overseas DNS if DNS leak protection is required." +msgstr "客户端日常访问域名解析,DNS泄漏要求高选择国外DNS。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:400 +msgid "Default DNS server for resolving domain name in the server address." +msgstr "填写国内DNS,Sing-box在连接这个代理节点前,需要先解析节点域名,这里就决定用哪个DNS去解析这个“代理服务器域名”,不要选走代理的国外 DNS,否则可能出现“代理还没连上,却要先通过代理解析代理服务器”的循环问题。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:900 msgid "Default DNS strategy" msgstr "默认 DNS 解析策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:357 -msgid "Default outbound" -msgstr "默认出站" +#: htdocs/luci-static/resources/view/homeproxy/client.js:380 +msgid "Default outbound (fallback)" +msgstr "默认出站(漏网之鱼)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:376 +#: htdocs/luci-static/resources/view/homeproxy/client.js:399 msgid "Default outbound DNS" msgstr "默认出站 DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:358 +#: htdocs/luci-static/resources/view/homeproxy/client.js:381 msgid "Default outbound for connections not matched by any routing rules." msgstr "用于未被任何路由规则匹配的连接的默认出站。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1389 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1829 msgid "Default packet encoding" msgstr "默认包封装格式" @@ -505,64 +539,79 @@ msgstr "默认包封装格式" msgid "Default server name" msgstr "默认服务器名称" -#: htdocs/luci-static/resources/view/homeproxy/client.js:364 -#: htdocs/luci-static/resources/view/homeproxy/client.js:466 -#: htdocs/luci-static/resources/view/homeproxy/client.js:684 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1033 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1382 -#: htdocs/luci-static/resources/view/homeproxy/node.js:428 +#: htdocs/luci-static/resources/view/homeproxy/client.js:387 +#: htdocs/luci-static/resources/view/homeproxy/client.js:489 +#: htdocs/luci-static/resources/view/homeproxy/client.js:707 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1405 +#: htdocs/luci-static/resources/view/homeproxy/node.js:858 msgid "Direct" msgstr "直连" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1514 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1537 msgid "Direct Domain List" msgstr "直连域名列表" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1428 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1473 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1451 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1496 msgid "Direct IPv4 IP-s" msgstr "直连 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1431 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1499 msgid "Direct IPv6 IP-s" msgstr "直连 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1434 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1457 msgid "Direct MAC-s" msgstr "直连 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:123 -#: htdocs/luci-static/resources/view/homeproxy/client.js:151 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 -#: htdocs/luci-static/resources/view/homeproxy/node.js:499 -#: htdocs/luci-static/resources/view/homeproxy/node.js:555 -#: htdocs/luci-static/resources/view/homeproxy/node.js:568 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1115 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1368 +#: htdocs/luci-static/resources/view/homeproxy/client.js:148 +#: htdocs/luci-static/resources/view/homeproxy/client.js:176 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:929 +#: htdocs/luci-static/resources/view/homeproxy/node.js:985 +#: htdocs/luci-static/resources/view/homeproxy/node.js:998 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1545 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1808 #: htdocs/luci-static/resources/view/homeproxy/server.js:276 #: htdocs/luci-static/resources/view/homeproxy/server.js:289 msgid "Disable" msgstr "禁用" -#: htdocs/luci-static/resources/view/homeproxy/client.js:363 +#: htdocs/luci-static/resources/view/homeproxy/client.js:386 msgid "Disable (the service)" msgstr "禁用(服务)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:787 -#: htdocs/luci-static/resources/view/homeproxy/client.js:899 +#: htdocs/luci-static/resources/view/homeproxy/client.js:810 +#: htdocs/luci-static/resources/view/homeproxy/client.js:922 msgid "Disable DNS cache" msgstr "禁用 DNS 缓存" -#: htdocs/luci-static/resources/view/homeproxy/client.js:788 +#: htdocs/luci-static/resources/view/homeproxy/client.js:811 msgid "Disable DNS cache in this query." msgstr "在本次查询中禁用 DNS 缓存。" +msgid "Disable DNS memory cache" +msgstr "禁用 DNS 内存缓存" + +msgid "Disable DNS memory cache in this query." +msgstr "在本次查询中禁用 DNS 内存缓存。" + +msgid "Disable DNS memory cache expiration" +msgstr "禁用 DNS 内存缓存过期" + +msgid "Independent DNS memory cache per server" +msgstr "每个 DNS 服务器独立内存缓存" + +msgid "Make each DNS server's memory cache independent for special purposes. If enabled, will slightly degrade performance." +msgstr "让每个 DNS 服务器使用独立内存缓存。启用后性能会略微下降。" + #: htdocs/luci-static/resources/view/homeproxy/server.js:670 msgid "Disable HTTP challenge" msgstr "禁用 HTTP 验证" -#: htdocs/luci-static/resources/view/homeproxy/node.js:605 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1035 #: htdocs/luci-static/resources/view/homeproxy/server.js:321 msgid "Disable Path MTU discovery" msgstr "禁用路径 MTU 探测" @@ -571,23 +620,26 @@ msgstr "禁用路径 MTU 探测" msgid "Disable TLS ALPN challenge" msgstr "禁用 TLS ALPN 认证" -#: htdocs/luci-static/resources/view/homeproxy/client.js:710 +#: htdocs/luci-static/resources/view/homeproxy/client.js:733 msgid "Disable UDP domain unmapping" msgstr "禁用 UDP 域名映射" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1182 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1205 msgid "Disable cache and save cache in this query." msgstr "在本次查询中禁用缓存。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:901 +msgid "Disable DNS memory cache and persistent cache in this query." +msgstr "在本次查询中禁用 DNS 内存缓存和持久化缓存。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:924 msgid "Disable cache expire" msgstr "缓存永不过期" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1181 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 msgid "Disable dns cache" msgstr "禁用 DNS 缓存" -#: htdocs/luci-static/resources/view/homeproxy/node.js:606 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1036 #: htdocs/luci-static/resources/view/homeproxy/server.js:322 msgid "" "Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 " @@ -596,37 +648,37 @@ msgstr "" "禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) " "字节。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:814 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1251 +#: htdocs/luci-static/resources/view/homeproxy/client.js:837 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1274 msgid "Domain keyword" msgstr "域名关键词" -#: htdocs/luci-static/resources/view/homeproxy/client.js:805 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:828 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1265 msgid "Domain name" msgstr "域名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:818 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1255 +#: htdocs/luci-static/resources/view/homeproxy/client.js:841 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1278 msgid "Domain regex" msgstr "域名正则表达式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:427 +#: htdocs/luci-static/resources/view/homeproxy/client.js:450 msgid "Domain resolver" msgstr "域名解析器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:347 -#: htdocs/luci-static/resources/view/homeproxy/client.js:446 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1174 +#: htdocs/luci-static/resources/view/homeproxy/client.js:370 +#: htdocs/luci-static/resources/view/homeproxy/client.js:469 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1197 msgid "Domain strategy" msgstr "域名解析策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:781 +#: htdocs/luci-static/resources/view/homeproxy/client.js:804 msgid "Domain strategy for resolving the domain names." msgstr "目标域名的解析策略。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:810 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1247 +#: htdocs/luci-static/resources/view/homeproxy/client.js:833 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1270 msgid "Domain suffix" msgstr "域名后缀" @@ -634,35 +686,35 @@ msgstr "域名后缀" msgid "Domains" msgstr "域名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:797 msgid "Don't drop packets" msgstr "不丢弃数据包" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1209 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1232 msgid "Don't drop requests" msgstr "不丢弃请求" -#: htdocs/luci-static/resources/view/homeproxy/node.js:989 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1419 #: htdocs/luci-static/resources/view/homeproxy/server.js:517 msgid "Download bandwidth" msgstr "下载带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:990 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1420 #: htdocs/luci-static/resources/view/homeproxy/server.js:518 msgid "Download bandwidth in Mbps." msgstr "下载带宽(单位:Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:770 -#: htdocs/luci-static/resources/view/homeproxy/client.js:776 +#: htdocs/luci-static/resources/view/homeproxy/client.js:793 +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 msgid "Drop packets" msgstr "丢弃数据包" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1234 msgid "Drop requests" msgstr "丢弃请求" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1375 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1815 msgid "" "Drop/keep nodes that contain the specific keywords. " "正则表达式。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1367 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1807 msgid "Drop/keep specific nodes from subscriptions." msgstr "从订阅中 丢弃/保留 指定节点" @@ -687,7 +739,7 @@ msgstr "" "
外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA " "客户数据库。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1094 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1524 msgid "" "ECH (Encrypted Client Hello) is a TLS extension that allows a client to " "encrypt the first part of its ClientHello message." @@ -695,12 +747,12 @@ msgstr "" "ECH(Encrypted Client Hello)是一个 TLS 扩展,它允许客户端加密其 ClientHello " "信息的第一部分。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1109 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1539 #: htdocs/luci-static/resources/view/homeproxy/server.js:835 msgid "ECH config" msgstr "ECH 配置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1098 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1528 msgid "ECH config path" msgstr "ECH 配置路径" @@ -708,18 +760,18 @@ msgstr "ECH 配置路径" msgid "ECH key" msgstr "ECH 密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:798 -#: htdocs/luci-static/resources/view/homeproxy/client.js:908 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1194 +#: htdocs/luci-static/resources/view/homeproxy/client.js:821 +#: htdocs/luci-static/resources/view/homeproxy/client.js:931 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1217 msgid "EDNS Client subnet" msgstr "ENDS 客户端子网" -#: htdocs/luci-static/resources/view/homeproxy/node.js:873 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1303 #: htdocs/luci-static/resources/view/homeproxy/server.js:481 msgid "Early data" msgstr "前置数据" -#: htdocs/luci-static/resources/view/homeproxy/node.js:880 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1310 #: htdocs/luci-static/resources/view/homeproxy/server.js:488 msgid "Early data header name" msgstr "前置数据标头" @@ -728,7 +780,7 @@ msgstr "前置数据标头" msgid "Early data is sent in path instead of header by default." msgstr "前置数据默认发送在路径而不是标头中。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1211 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1644 msgid "Edit nodes" msgstr "修改节点" @@ -736,17 +788,17 @@ msgstr "修改节点" msgid "Email" msgstr "Email" -#: htdocs/luci-static/resources/view/homeproxy/client.js:414 -#: htdocs/luci-static/resources/view/homeproxy/client.js:590 -#: htdocs/luci-static/resources/view/homeproxy/client.js:942 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1070 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1329 +#: htdocs/luci-static/resources/view/homeproxy/client.js:437 +#: htdocs/luci-static/resources/view/homeproxy/client.js:613 +#: htdocs/luci-static/resources/view/homeproxy/client.js:965 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1352 #: htdocs/luci-static/resources/view/homeproxy/server.js:148 #: htdocs/luci-static/resources/view/homeproxy/server.js:166 msgid "Enable" msgstr "启用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:722 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1152 #: htdocs/luci-static/resources/view/homeproxy/server.js:371 msgid "" "Enable 0-RTT QUIC connection handshake on the client side. This is not " @@ -756,7 +808,7 @@ msgstr "" "在客户端启用 0-RTT QUIC 连接握手。由于协议是完全复用的,这对性能影响不大。" "
强烈建议禁用此功能,因为它容易受到重放攻击。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:721 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1151 #: htdocs/luci-static/resources/view/homeproxy/server.js:370 msgid "Enable 0-RTT handshake" msgstr "启用 0-RTT 握手" @@ -765,30 +817,30 @@ msgstr "启用 0-RTT 握手" msgid "Enable ACME" msgstr "启用 ACME" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1523 msgid "Enable ECH" msgstr "启用 ECH" -#: htdocs/luci-static/resources/view/homeproxy/node.js:984 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1414 #: htdocs/luci-static/resources/view/homeproxy/server.js:512 msgid "Enable TCP Brutal" msgstr "启用 TCP Brutal" -#: htdocs/luci-static/resources/view/homeproxy/node.js:985 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1415 #: htdocs/luci-static/resources/view/homeproxy/server.js:513 msgid "Enable TCP Brutal congestion control algorithm" msgstr "启用 TCP Brutal 拥塞控制算法。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1168 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1598 #: htdocs/luci-static/resources/view/homeproxy/server.js:855 msgid "Enable UDP fragmentation." msgstr "启用 UDP 分片。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:328 +#: htdocs/luci-static/resources/view/homeproxy/client.js:351 msgid "Enable endpoint-independent NAT" msgstr "启用端点独立 NAT" -#: htdocs/luci-static/resources/view/homeproxy/node.js:980 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1410 #: htdocs/luci-static/resources/view/homeproxy/server.js:507 msgid "Enable padding" msgstr "启用填充" @@ -797,17 +849,18 @@ msgstr "启用填充" msgid "Enable tcp fast open for listener." msgstr "为监听器启用 TCP 快速打开。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1172 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1602 msgid "" "Enable the SUoT protocol, requires server support. Conflict with multiplex." msgstr "启用 SUoT 协议,需要服务端支持。与多路复用冲突。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:612 -#: htdocs/luci-static/resources/view/homeproxy/node.js:748 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1042 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 #: htdocs/luci-static/resources/view/homeproxy/server.js:338 msgid "Encrypt method" msgstr "加密方式" +#: htdocs/luci-static/resources/view/homeproxy/node.js:488 #: htdocs/luci-static/resources/view/homeproxy/status.js:139 msgid "Error" msgstr "错误" @@ -820,26 +873,26 @@ msgstr "错误" #: htdocs/luci-static/resources/homeproxy.js:317 #: htdocs/luci-static/resources/homeproxy.js:326 #: htdocs/luci-static/resources/homeproxy.js:328 -#: htdocs/luci-static/resources/view/homeproxy/client.js:195 -#: htdocs/luci-static/resources/view/homeproxy/client.js:207 -#: htdocs/luci-static/resources/view/homeproxy/client.js:211 -#: htdocs/luci-static/resources/view/homeproxy/client.js:230 -#: htdocs/luci-static/resources/view/homeproxy/client.js:241 -#: htdocs/luci-static/resources/view/homeproxy/client.js:245 -#: htdocs/luci-static/resources/view/homeproxy/client.js:274 -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:518 -#: htdocs/luci-static/resources/view/homeproxy/client.js:521 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1362 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1539 -#: htdocs/luci-static/resources/view/homeproxy/node.js:488 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1302 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1359 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:231 +#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:268 +#: htdocs/luci-static/resources/view/homeproxy/client.js:297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:527 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1380 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1562 +#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1735 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1796 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1799 #: htdocs/luci-static/resources/view/homeproxy/server.js:235 #: htdocs/luci-static/resources/view/homeproxy/server.js:618 #: htdocs/luci-static/resources/view/homeproxy/server.js:620 @@ -858,7 +911,7 @@ msgstr "外部账户 MAC 密钥" msgid "External account key ID" msgstr "外部账户密钥标识符" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1237 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1260 msgid "Extra records" msgstr "附加记录" @@ -870,19 +923,53 @@ msgstr "生成 %s 失败,错误:%s。" msgid "Failed to upload %s, error: %s." msgstr "上传 %s 失败,错误:%s。" +#: htdocs/luci-static/resources/homeproxy.js +msgid "panel ZIP package" +msgstr "面版ZIP压缩包" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "unknown error" +msgstr "未知错误" + +#: htdocs/luci-static/resources/homeproxy.js +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Backup panel failed." +msgstr "备份面版失败。" + +#: htdocs/luci-static/resources/homeproxy.js +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Restore panel failed." +msgstr "恢复面版失败。" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Panel ZIP file is empty." +msgstr "面版 ZIP 文件为空。" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "The unzip command is unavailable." +msgstr "unzip 命令不可用。" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Invalid panel ZIP file." +msgstr "面版 ZIP 文件无效。" + +#: htdocs/luci-static/resources/homeproxy.js +msgid "Install panel failed." +msgstr "安装面版失败。" + #: htdocs/luci-static/resources/view/homeproxy/status.js:140 msgid "Fatal" msgstr "致命" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1374 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1814 msgid "Filter keywords" msgstr "过滤关键词" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1806 msgid "Filter nodes" msgstr "过滤节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:630 +#: htdocs/luci-static/resources/view/homeproxy/client.js:653 msgid "Firefox / uquic firefox" msgstr "Firefox / uquic firefox" @@ -890,33 +977,33 @@ msgstr "Firefox / uquic firefox" msgid "Firewall" msgstr "防火墙" -#: htdocs/luci-static/resources/view/homeproxy/node.js:736 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1166 #: htdocs/luci-static/resources/view/homeproxy/server.js:385 msgid "Flow" msgstr "流控" -#: htdocs/luci-static/resources/view/homeproxy/client.js:428 +#: htdocs/luci-static/resources/view/homeproxy/client.js:451 msgid "For resolving domain name in the server address." msgstr "用于解析服务器地址中的域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1340 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1363 msgid "Format" msgstr "格式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:730 +#: htdocs/luci-static/resources/view/homeproxy/client.js:753 msgid "Fragment TLS handshake into multiple TLS records." msgstr "将 TLS 握手分片为多个 TLS 记录" -#: htdocs/luci-static/resources/view/homeproxy/client.js:736 +#: htdocs/luci-static/resources/view/homeproxy/client.js:759 msgid "" "Fragment TLS handshakes. Due to poor performance, try %s first." msgstr "分片 TLS 握手。由于性能较差,请先尝试 %s。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:742 +#: htdocs/luci-static/resources/view/homeproxy/client.js:765 msgid "Fragment fallback delay" msgstr "分片回退延迟" -#: htdocs/luci-static/resources/view/homeproxy/node.js:842 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1272 msgid "GET" msgstr "GET" @@ -924,19 +1011,19 @@ msgstr "GET" msgid "GFW list version" msgstr "GFW 域名列表版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:252 +#: htdocs/luci-static/resources/view/homeproxy/client.js:275 msgid "GFWList" msgstr "GFWList" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1446 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1469 msgid "Gaming mode IPv4 IP-s" msgstr "游戏模式 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1448 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1471 msgid "Gaming mode IPv6 IP-s" msgstr "游戏模式 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1451 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1474 msgid "Gaming mode MAC-s" msgstr "游戏模式 MAC 地址" @@ -950,23 +1037,23 @@ msgstr "生成" msgid "GitHub token" msgstr "GitHub 令牌" -#: htdocs/luci-static/resources/view/homeproxy/client.js:256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:279 msgid "Global" msgstr "全局" -#: htdocs/luci-static/resources/view/homeproxy/node.js:759 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1189 msgid "Global padding" msgstr "全局填充" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1453 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1476 msgid "Global proxy IPv4 IP-s" msgstr "全局代理 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1456 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1479 msgid "Global proxy IPv6 IP-s" msgstr "全局代理 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1459 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1482 msgid "Global proxy MAC-s" msgstr "全局代理 MAC 地址" @@ -978,24 +1065,24 @@ msgstr "全局设置" msgid "Google" msgstr "谷歌" -#: htdocs/luci-static/resources/view/homeproxy/client.js:184 -msgid "Google Public DNS (8.8.8.8)" -msgstr "谷歌公共 DNS(8.8.8.8)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:208 +msgid "Google Public DNS (DoH)" +msgstr "Google 公共DNS (DoH)" #: root/usr/share/rpcd/acl.d/luci-app-homeproxy.json:3 msgid "Grant access to homeproxy configuration" msgstr "授予 homeproxy 访问 UCI 配置的权限" -#: htdocs/luci-static/resources/view/homeproxy/client.js:620 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1108 -#: htdocs/luci-static/resources/view/homeproxy/node.js:430 -#: htdocs/luci-static/resources/view/homeproxy/node.js:777 +#: htdocs/luci-static/resources/view/homeproxy/client.js:643 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/node.js:860 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1207 #: htdocs/luci-static/resources/view/homeproxy/server.js:177 #: htdocs/luci-static/resources/view/homeproxy/server.js:403 msgid "HTTP" msgstr "HTTP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:952 +#: htdocs/luci-static/resources/view/homeproxy/client.js:975 msgid "HTTP/3" msgstr "HTTP/3" @@ -1005,11 +1092,11 @@ msgid "" "returned if empty." msgstr "身份验证失败时的 HTTP3 服务器响应。默认返回 404 页面。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:974 msgid "HTTPS" msgstr "HTTPS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:778 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1208 #: htdocs/luci-static/resources/view/homeproxy/server.js:404 msgid "HTTPUpgrade" msgstr "HTTPUpgrade" @@ -1022,18 +1109,18 @@ msgstr "握手服务器地址" msgid "Handshake server port" msgstr "握手服务器端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:974 +#: htdocs/luci-static/resources/view/homeproxy/client.js:997 msgid "Headers" msgstr "标头" -#: htdocs/luci-static/resources/view/homeproxy/node.js:727 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1157 #: htdocs/luci-static/resources/view/homeproxy/server.js:376 msgid "Heartbeat interval" msgstr "心跳间隔" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/client.js:63 +#: htdocs/luci-static/resources/view/homeproxy/client.js:65 +#: htdocs/luci-static/resources/view/homeproxy/client.js:112 #: htdocs/luci-static/resources/view/homeproxy/status.js:282 #: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3 msgid "HomeProxy" @@ -1045,33 +1132,33 @@ msgstr "HomeProxy" msgid "HomeProxy Server" msgstr "HomeProxy 服务端" -#: htdocs/luci-static/resources/view/homeproxy/node.js:535 +#: htdocs/luci-static/resources/view/homeproxy/node.js:965 msgid "Hop interval" msgstr "跳跃间隔" -#: htdocs/luci-static/resources/view/homeproxy/node.js:529 +#: htdocs/luci-static/resources/view/homeproxy/node.js:959 msgid "Hopping port" msgstr "跳跃端口" -#: htdocs/luci-static/resources/view/homeproxy/node.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:831 -#: htdocs/luci-static/resources/view/homeproxy/node.js:865 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1256 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1261 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1295 #: htdocs/luci-static/resources/view/homeproxy/server.js:437 #: htdocs/luci-static/resources/view/homeproxy/server.js:442 #: htdocs/luci-static/resources/view/homeproxy/server.js:473 msgid "Host" msgstr "主机名" -#: htdocs/luci-static/resources/view/homeproxy/node.js:669 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1099 msgid "Host key" msgstr "主机密钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:674 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1104 msgid "Host key algorithms" msgstr "主机密钥算法" -#: htdocs/luci-static/resources/view/homeproxy/client.js:581 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1061 +#: htdocs/luci-static/resources/view/homeproxy/client.js:604 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1084 msgid "Host/IP fields" msgstr "主机/IP 字段" @@ -1081,28 +1168,44 @@ msgid "" "command (in seconds)." msgstr "服务器等待客户端发送认证命令的时间(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:432 +#: htdocs/luci-static/resources/view/homeproxy/node.js:862 #: htdocs/luci-static/resources/view/homeproxy/server.js:179 msgid "Hysteria" msgstr "Hysteria" -#: htdocs/luci-static/resources/view/homeproxy/node.js:433 +#: htdocs/luci-static/resources/view/homeproxy/node.js:863 #: htdocs/luci-static/resources/view/homeproxy/server.js:180 msgid "Hysteria2" msgstr "Hysteria2" -#: htdocs/luci-static/resources/view/homeproxy/client.js:830 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1267 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1784 +msgid "ICMP" +msgstr "ICMP" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:474 +msgid "ICMP Failed" +msgstr "ICMP 失败" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:476 +msgid "ICMP Probe unavailable" +msgstr "ICMP 探测不可用" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:478 +msgid "ICMP Timeout" +msgstr "ICMP 超时" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:853 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1290 msgid "IP CIDR" msgstr "IP CIDR" -#: htdocs/luci-static/resources/view/homeproxy/client.js:608 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +#: htdocs/luci-static/resources/view/homeproxy/client.js:631 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 msgid "IP version" msgstr "IP 版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:610 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/client.js:633 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 msgid "IPv4" msgstr "IPv4" @@ -1110,8 +1213,8 @@ msgstr "IPv4" msgid "IPv4 only" msgstr "仅 IPv4" -#: htdocs/luci-static/resources/view/homeproxy/client.js:611 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1091 +#: htdocs/luci-static/resources/view/homeproxy/client.js:634 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1114 msgid "IPv6" msgstr "IPv6" @@ -1119,31 +1222,31 @@ msgstr "IPv6" msgid "IPv6 only" msgstr "仅 IPv6" -#: htdocs/luci-static/resources/view/homeproxy/client.js:297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:320 msgid "IPv6 support" msgstr "IPv6 支持" -#: htdocs/luci-static/resources/view/homeproxy/node.js:506 +#: htdocs/luci-static/resources/view/homeproxy/node.js:936 msgid "Idle session check interval" msgstr "空闲会话检查间隔" -#: htdocs/luci-static/resources/view/homeproxy/node.js:513 +#: htdocs/luci-static/resources/view/homeproxy/node.js:943 msgid "Idle session check timeout" msgstr "空闲会话检查超时" -#: htdocs/luci-static/resources/view/homeproxy/client.js:553 -#: htdocs/luci-static/resources/view/homeproxy/node.js:847 +#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1277 #: htdocs/luci-static/resources/view/homeproxy/server.js:456 msgid "Idle timeout" msgstr "空闲超时" -#: htdocs/luci-static/resources/view/homeproxy/client.js:717 +#: htdocs/luci-static/resources/view/homeproxy/client.js:740 msgid "" "If enabled, attempts to connect UDP connection to the destination instead of " "listen." msgstr "如果启用,尝试主动连接到目标的 UDP 而不是监听。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:711 +#: htdocs/luci-static/resources/view/homeproxy/client.js:734 msgid "" "If enabled, for UDP proxy requests addressed to a domain, the original " "packet address will be sent in the response instead of the mapped domain." @@ -1151,18 +1254,18 @@ msgstr "" "如果启用,对于发送到域名的 UDP 代理请求,响应中将发送原始数据包地址而不是映射" "的域名。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:819 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1249 msgid "" "If enabled, the client transport sends keepalive pings even with no active " "connections." msgstr "如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:348 +#: htdocs/luci-static/resources/view/homeproxy/client.js:371 msgid "" "If set, the requested domain name will be resolved to IP before routing." msgstr "如果设置,请求的域名将在路由前被解析为 IP 地址。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:804 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1234 #: htdocs/luci-static/resources/view/homeproxy/server.js:425 msgid "" "If the transport doesn't see any activity after a duration of this time (in " @@ -1171,7 +1274,7 @@ msgstr "" "如果传输在此时间段(单位:秒)后没有看到任何活动,它会向客户端发送 ping 请求" "以检查连接是否仍然活动。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1073 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1503 msgid "" "If you have the root certificate, use this option instead of allowing " "insecure." @@ -1181,37 +1284,37 @@ msgstr "如果你拥有根证书,使用此选项而不是允许不安全连接 msgid "Ignore client bandwidth" msgstr "忽略客户端带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1285 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1718 msgid "Import" msgstr "导入" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1232 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1313 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1665 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1744 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1746 msgid "Import share links" msgstr "导入分享链接" -#: htdocs/luci-static/resources/view/homeproxy/client.js:336 +#: htdocs/luci-static/resources/view/homeproxy/client.js:359 #: htdocs/luci-static/resources/view/homeproxy/server.js:860 msgid "In seconds." msgstr "单位:秒。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:937 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1367 msgid "In seconds. Disabled by default." msgstr "单位:秒。默认禁用。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:521 +#: htdocs/luci-static/resources/view/homeproxy/node.js:951 msgid "" "In the check, at least the first n idle sessions are kept open." msgstr "在检查中,至少保持前 n 个空闲会话处于打开状态。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:514 +#: htdocs/luci-static/resources/view/homeproxy/node.js:944 msgid "" "In the check, close sessions that have been idle for longer than this, in " "seconds." msgstr "在检查中,关闭空闲时间超过此值的会话(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:904 +#: htdocs/luci-static/resources/view/homeproxy/client.js:927 msgid "Independent cache per server" msgstr "独立缓存" @@ -1219,36 +1322,45 @@ msgstr "独立缓存" msgid "Info" msgstr "信息" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1428 msgid "Interface Control" msgstr "接口控制" -#: htdocs/luci-static/resources/view/homeproxy/client.js:560 +#: htdocs/luci-static/resources/view/homeproxy/client.js:583 msgid "Interrupt existing connections" msgstr "中断现有连接" -#: htdocs/luci-static/resources/view/homeproxy/client.js:561 +#: htdocs/luci-static/resources/view/homeproxy/client.js:584 msgid "Interrupt existing connections when the selected outbound has changed." msgstr "当选择的出站发生变化时中断现有连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:507 +#: htdocs/luci-static/resources/view/homeproxy/node.js:937 msgid "Interval checking for idle sessions, in seconds." msgstr "空闲会话检查间隔(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:728 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1158 #: htdocs/luci-static/resources/view/homeproxy/server.js:377 msgid "" "Interval for sending heartbeat packets for keeping the connection alive (in " "seconds)." msgstr "发送心跳包以保持连接存活的时间间隔(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:665 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:128 +#: htdocs/luci-static/resources/view/homeproxy/node.js:480 +msgid "Invalid node" +msgstr "无效节点" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:482 +msgid "Invalid response" +msgstr "无效响应" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:688 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1165 msgid "Invert" msgstr "反转" -#: htdocs/luci-static/resources/view/homeproxy/client.js:666 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 +#: htdocs/luci-static/resources/view/homeproxy/client.js:689 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1166 msgid "Invert match result." msgstr "反转匹配结果" @@ -1256,21 +1368,29 @@ msgstr "反转匹配结果" msgid "Key path" msgstr "证书路径" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1419 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1442 msgid "LAN IP Policy" msgstr "LAN IP 策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:409 -#: htdocs/luci-static/resources/view/homeproxy/client.js:585 -#: htdocs/luci-static/resources/view/homeproxy/client.js:937 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1065 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1324 -#: htdocs/luci-static/resources/view/homeproxy/node.js:422 +#: htdocs/luci-static/resources/view/homeproxy/client.js:432 +#: htdocs/luci-static/resources/view/homeproxy/client.js:608 +#: htdocs/luci-static/resources/view/homeproxy/client.js:960 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1347 +#: htdocs/luci-static/resources/view/homeproxy/node.js:852 #: htdocs/luci-static/resources/view/homeproxy/server.js:160 msgid "Label" msgstr "标签" -#: htdocs/luci-static/resources/view/homeproxy/node.js:743 +#: htdocs/luci-static/resources/view/homeproxy/node.js:817 +msgid "Latency" +msgstr "延迟" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1782 +msgid "Latency test mode" +msgstr "延迟测试模式" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1173 #: htdocs/luci-static/resources/view/homeproxy/server.js:392 msgid "" "Legacy protocol support (VMess MD5 Authentication) is provided for " @@ -1279,7 +1399,7 @@ msgstr "" "提供旧协议支持(VMess MD5 身份验证)仅出于兼容性目的,不建议使用 alterId > " "1。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:325 +#: htdocs/luci-static/resources/view/homeproxy/client.js:348 msgid "Less compatibility and sometimes better performance." msgstr "有时性能更好。" @@ -1287,31 +1407,31 @@ msgstr "有时性能更好。" msgid "Let's Encrypt" msgstr "Let's Encrypt" -#: htdocs/luci-static/resources/view/homeproxy/node.js:897 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1327 msgid "" "List of IP (v4 or v6) addresses prefixes to be assigned to the interface." msgstr "分配给接口的 IP(v4 或 v6)地址前缀列表。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:132 -#: htdocs/luci-static/resources/view/homeproxy/client.js:161 -#: htdocs/luci-static/resources/view/homeproxy/client.js:497 +#: htdocs/luci-static/resources/view/homeproxy/client.js:157 +#: htdocs/luci-static/resources/view/homeproxy/client.js:186 +#: htdocs/luci-static/resources/view/homeproxy/client.js:520 msgid "List of nodes to test." msgstr "要测试的节点列表。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1036 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1466 #: htdocs/luci-static/resources/view/homeproxy/server.js:566 msgid "List of supported application level protocols, in order of preference." msgstr "支持的应用层协议协商列表,按顺序排列。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1228 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1251 msgid "List of text DNS record to respond as answers." msgstr "要响应的 DNS 记录列表。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1238 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1261 msgid "List of text DNS record to respond as extra records." msgstr "要响应的附加 DNS 记录列表。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1233 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1256 msgid "List of text DNS record to respond as name servers." msgstr "要响应的域名服务器(NS) DNS 记录列表。" @@ -1319,7 +1439,7 @@ msgstr "要响应的域名服务器(NS) DNS 记录列表。" msgid "Listen address" msgstr "监听地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1407 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1430 msgid "Listen interfaces" msgstr "监听接口" @@ -1331,11 +1451,11 @@ msgstr "监听端口" msgid "Loading" msgstr "加载中" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1358 msgid "Local" msgstr "本地" -#: htdocs/luci-static/resources/view/homeproxy/node.js:896 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1326 msgid "Local address" msgstr "本地地址" @@ -1347,31 +1467,31 @@ msgstr "日志文件不存在。" msgid "Log is empty." msgstr "日志为空。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:930 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1360 msgid "MTU" msgstr "MTU" -#: htdocs/luci-static/resources/view/homeproxy/client.js:150 +#: htdocs/luci-static/resources/view/homeproxy/client.js:175 msgid "Main UDP node" msgstr "主 UDP 节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:122 +#: htdocs/luci-static/resources/view/homeproxy/client.js:147 msgid "Main node" msgstr "主节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:662 +#: htdocs/luci-static/resources/view/homeproxy/client.js:685 msgid "Make IP CIDR in rule set used to match the source IP." msgstr "使规则集中的 IP CIDR 用于匹配源 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1158 msgid "Make IP CIDR in rule sets match the source IP." msgstr "使规则集中的 IP CIDR 匹配源 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 msgid "Make IP CIDR in rule-sets accept empty query response." msgstr "使规则集中的 IP CIDR 接受空查询响应。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:905 +#: htdocs/luci-static/resources/view/homeproxy/client.js:928 msgid "" "Make each DNS server's cache independent for special purposes. If enabled, " "will slightly degrade performance." @@ -1381,109 +1501,109 @@ msgstr "独立缓存每个 DNS 服务器的结果以供特殊用途。启用后 msgid "Masquerade" msgstr "伪装" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1268 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1291 msgid "" "Match IP CIDR with query response. Current rule will be skipped if not match." msgstr "使用查询响应匹配 IP CIDR。如果不匹配,则跳过当前规则。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:831 +#: htdocs/luci-static/resources/view/homeproxy/client.js:854 msgid "Match IP CIDR." msgstr "匹配 IP CIDR。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:811 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1248 +#: htdocs/luci-static/resources/view/homeproxy/client.js:834 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 msgid "Match domain suffix." msgstr "匹配域名后缀。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:815 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1252 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1275 msgid "Match domain using keyword." msgstr "使用关键词匹配域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:819 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:842 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1279 msgid "Match domain using regular expression." msgstr "使用正则表达式匹配域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:806 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1243 +#: htdocs/luci-static/resources/view/homeproxy/client.js:829 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1266 msgid "Match full domain." msgstr "匹配完整域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:854 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1292 +#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1315 msgid "Match port range. Format as START:/:END/START:END." msgstr "匹配端口范围。格式为 START:/:END/START:END。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:849 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1287 +#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1310 msgid "Match port." msgstr "匹配端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:835 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1272 +#: htdocs/luci-static/resources/view/homeproxy/client.js:858 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1295 msgid "Match private IP" msgstr "匹配私有 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1273 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1296 msgid "Match private IP with query response." msgstr "使用查询响应匹配私有 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:827 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:850 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1287 msgid "Match private source IP" msgstr "匹配私有源 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:859 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1297 +#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 msgid "Match process name." msgstr "匹配进程名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:867 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1305 +#: htdocs/luci-static/resources/view/homeproxy/client.js:890 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1328 msgid "Match process path using regular expression." msgstr "使用正则表达式匹配进程路径。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:863 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1301 +#: htdocs/luci-static/resources/view/homeproxy/client.js:886 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1324 msgid "Match process path." msgstr "匹配进程路径。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1096 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1119 msgid "Match query type." msgstr "匹配请求类型。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:647 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1120 +#: htdocs/luci-static/resources/view/homeproxy/client.js:670 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 msgid "Match rule set." msgstr "匹配规则集。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:823 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1260 +#: htdocs/luci-static/resources/view/homeproxy/client.js:846 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1283 msgid "Match source IP CIDR." msgstr "匹配源 IP CIDR。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:844 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1282 +#: htdocs/luci-static/resources/view/homeproxy/client.js:867 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1305 msgid "Match source port range. Format as START:/:END/START:END." msgstr "匹配源端口范围。格式为 START:/:END/START:END。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:839 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1277 +#: htdocs/luci-static/resources/view/homeproxy/client.js:862 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1300 msgid "Match source port." msgstr "匹配源端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:643 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1116 +#: htdocs/luci-static/resources/view/homeproxy/client.js:666 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1139 msgid "Match user name." msgstr "匹配用户名。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:579 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1009 #: htdocs/luci-static/resources/view/homeproxy/server.js:261 msgid "Max download speed" msgstr "最大下载速度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:580 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1010 #: htdocs/luci-static/resources/view/homeproxy/server.js:262 msgid "Max download speed in Mbps." msgstr "最大下载速度(Mbps)。" @@ -1492,27 +1612,27 @@ msgstr "最大下载速度(Mbps)。" msgid "Max time difference" msgstr "最大时间差" -#: htdocs/luci-static/resources/view/homeproxy/node.js:586 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1016 #: htdocs/luci-static/resources/view/homeproxy/server.js:268 msgid "Max upload speed" msgstr "最大上传速度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:587 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1017 #: htdocs/luci-static/resources/view/homeproxy/server.js:269 msgid "Max upload speed in Mbps." msgstr "最大上传速度(Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1056 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1486 #: htdocs/luci-static/resources/view/homeproxy/server.js:578 msgid "Maximum TLS version" msgstr "最大 TLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:961 -#: htdocs/luci-static/resources/view/homeproxy/node.js:975 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1391 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1405 msgid "Maximum connections" msgstr "最大连接数" -#: htdocs/luci-static/resources/view/homeproxy/node.js:973 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1403 msgid "" "Maximum multiplexed streams in a connection before opening a new connection." "
Conflict with %s and %s." @@ -1520,80 +1640,84 @@ msgstr "" "在打开新连接之前,连接中的最大多路复用流数量。与 %s 和 " "%s 冲突。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:972 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1402 msgid "Maximum streams" msgstr "最大流数量" -#: htdocs/luci-static/resources/view/homeproxy/client.js:768 -#: htdocs/luci-static/resources/view/homeproxy/client.js:776 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 -#: htdocs/luci-static/resources/view/homeproxy/node.js:841 +#: htdocs/luci-static/resources/view/homeproxy/client.js:791 +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1225 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1234 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1271 #: htdocs/luci-static/resources/view/homeproxy/server.js:452 msgid "Method" msgstr "方式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1048 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1478 #: htdocs/luci-static/resources/view/homeproxy/server.js:570 msgid "Minimum TLS version" msgstr "最低 TLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:520 +#: htdocs/luci-static/resources/view/homeproxy/node.js:950 msgid "Minimum idle sessions" msgstr "最小空闲会话数" -#: htdocs/luci-static/resources/view/homeproxy/node.js:967 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1397 msgid "" "Minimum multiplexed streams in a connection before opening a new connection." msgstr "在打开新连接之前,连接中的最小多路复用流数量。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:966 -#: htdocs/luci-static/resources/view/homeproxy/node.js:975 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1405 msgid "Minimum streams" msgstr "最小流数量" -#: htdocs/luci-static/resources/view/homeproxy/client.js:310 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1773 +msgid "Minutes(0-59) Hours(0-23) Dates(1-31) Months(1-12) Weeks(0-6)" +msgstr "分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-6)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:333 #: htdocs/luci-static/resources/view/homeproxy/server.js:183 msgid "Mixed" msgstr "混合" -#: htdocs/luci-static/resources/view/homeproxy/client.js:321 +#: htdocs/luci-static/resources/view/homeproxy/client.js:344 msgid "Mixed system TCP stack and gVisor UDP stack." msgstr "混合系统 TCP 栈和 gVisor UDP 栈。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:595 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1075 +#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1098 msgid "Mode" msgstr "模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1164 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1594 #: htdocs/luci-static/resources/view/homeproxy/server.js:850 msgid "MultiPath TCP" msgstr "多路径 TCP(MPTCP)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:944 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1374 #: htdocs/luci-static/resources/view/homeproxy/server.js:500 msgid "Multiplex" msgstr "多路复用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:952 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1382 msgid "Multiplex protocol." msgstr "多路复用协议。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 +#: htdocs/luci-static/resources/view/homeproxy/client.js:65 #: htdocs/luci-static/resources/view/homeproxy/server.js:60 msgid "NOT RUNNING" msgstr "未运行" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1395 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1835 msgid "NOTE: Save current settings before updating subscriptions." msgstr "注意:更新订阅前先保存当前配置。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1232 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1255 msgid "NS" msgstr "NS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:711 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1141 msgid "Native" msgstr "原生" @@ -1601,42 +1725,42 @@ msgstr "原生" msgid "NaïveProxy" msgstr "NaïveProxy" -#: htdocs/luci-static/resources/view/homeproxy/client.js:637 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1099 +#: htdocs/luci-static/resources/view/homeproxy/client.js:660 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 #: htdocs/luci-static/resources/view/homeproxy/server.js:866 msgid "Network" msgstr "网络" -#: htdocs/luci-static/resources/view/homeproxy/node.js:701 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 msgid "New Reno" msgstr "New Reno" -#: htdocs/luci-static/resources/view/homeproxy/node.js:774 -#: htdocs/luci-static/resources/view/homeproxy/node.js:791 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1204 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1221 #: htdocs/luci-static/resources/view/homeproxy/server.js:400 #: htdocs/luci-static/resources/view/homeproxy/server.js:417 msgid "No TCP transport, plain HTTP is merged into the HTTP transport." msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:789 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1219 #: htdocs/luci-static/resources/view/homeproxy/server.js:415 msgid "No additional encryption support: It's basically duplicate encryption." msgstr "无额外加密支持:它基本上是重复加密。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1412 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1852 msgid "No subscription available" msgstr "无可用订阅" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1877 msgid "No subscription node" msgstr "无订阅节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1271 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1704 msgid "No valid share link found." msgstr "找不到有效分享链接。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:419 -#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 msgid "Node" msgstr "节点" @@ -1644,81 +1768,86 @@ msgstr "节点" msgid "Node Settings" msgstr "节点设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1217 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1650 msgid "Nodes" msgstr "节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:994 -#: htdocs/luci-static/resources/view/homeproxy/node.js:737 -#: htdocs/luci-static/resources/view/homeproxy/node.js:775 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1167 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1205 #: htdocs/luci-static/resources/view/homeproxy/server.js:386 #: htdocs/luci-static/resources/view/homeproxy/server.js:401 msgid "None" msgstr "无" -#: htdocs/luci-static/resources/view/homeproxy/node.js:573 +#: htdocs/luci-static/resources/view/homeproxy/node.js:494 +#: htdocs/luci-static/resources/view/homeproxy/node.js:507 +msgid "Not Tested" +msgstr "未测试" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1003 #: htdocs/luci-static/resources/view/homeproxy/server.js:294 msgid "Obfuscate password" msgstr "混淆密码" -#: htdocs/luci-static/resources/view/homeproxy/node.js:567 +#: htdocs/luci-static/resources/view/homeproxy/node.js:997 #: htdocs/luci-static/resources/view/homeproxy/server.js:288 msgid "Obfuscate type" msgstr "混淆类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1408 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1431 msgid "Only process traffic from specific interfaces. Leave empty for all." msgstr "只处理来自指定接口的流量。留空表示全部。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:254 +#: htdocs/luci-static/resources/view/homeproxy/client.js:277 msgid "Only proxy mainland China" msgstr "仅代理中国大陆" -#: htdocs/luci-static/resources/view/homeproxy/client.js:580 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1060 +#: htdocs/luci-static/resources/view/homeproxy/client.js:603 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1083 msgid "Other fields" msgstr "其他字段" -#: htdocs/luci-static/resources/view/homeproxy/client.js:460 -#: htdocs/luci-static/resources/view/homeproxy/client.js:678 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1027 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1375 +#: htdocs/luci-static/resources/view/homeproxy/client.js:483 +#: htdocs/luci-static/resources/view/homeproxy/client.js:701 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1050 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1398 msgid "Outbound" msgstr "出站" -#: htdocs/luci-static/resources/view/homeproxy/client.js:420 +#: htdocs/luci-static/resources/view/homeproxy/client.js:443 msgid "Outbound node" msgstr "出站节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:696 +#: htdocs/luci-static/resources/view/homeproxy/client.js:719 msgid "Override address" msgstr "覆盖地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:352 +#: htdocs/luci-static/resources/view/homeproxy/client.js:375 msgid "Override destination" msgstr "覆盖目标地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:703 +#: htdocs/luci-static/resources/view/homeproxy/client.js:726 msgid "Override port" msgstr "覆盖端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:353 +#: htdocs/luci-static/resources/view/homeproxy/client.js:376 msgid "Override the connection destination address with the sniffed domain." msgstr "使用嗅探到的域名覆盖连接目标。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:697 +#: htdocs/luci-static/resources/view/homeproxy/client.js:720 msgid "Override the connection destination address." msgstr "覆盖目标连接地址。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:704 +#: htdocs/luci-static/resources/view/homeproxy/client.js:727 msgid "Override the connection destination port." msgstr "覆盖目标连接端口。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:843 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1273 msgid "PUT" msgstr "PUT" -#: htdocs/luci-static/resources/view/homeproxy/node.js:886 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1316 msgid "Packet encoding" msgstr "数据包编码" @@ -1730,77 +1859,77 @@ msgstr "填充方案" msgid "Panic" msgstr "崩溃" -#: htdocs/luci-static/resources/view/homeproxy/node.js:464 +#: htdocs/luci-static/resources/view/homeproxy/node.js:894 #: htdocs/luci-static/resources/view/homeproxy/server.js:210 msgid "Password" msgstr "密码" -#: htdocs/luci-static/resources/view/homeproxy/client.js:967 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1346 -#: htdocs/luci-static/resources/view/homeproxy/node.js:836 -#: htdocs/luci-static/resources/view/homeproxy/node.js:869 +#: htdocs/luci-static/resources/view/homeproxy/client.js:990 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1369 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1266 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1299 #: htdocs/luci-static/resources/view/homeproxy/server.js:447 #: htdocs/luci-static/resources/view/homeproxy/server.js:477 msgid "Path" msgstr "路径" -#: htdocs/luci-static/resources/view/homeproxy/node.js:911 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1341 msgid "Peer pubkic key" msgstr "对端公钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:329 +#: htdocs/luci-static/resources/view/homeproxy/client.js:352 msgid "" "Performance may degrade slightly, so it is not recommended to enable on when " "it is not needed." msgstr "性能可能会略有下降,建议仅在需要时开启。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:936 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 msgid "Persistent keepalive interval" msgstr "持久 keepalive 间隔" -#: htdocs/luci-static/resources/view/homeproxy/node.js:855 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1285 #: htdocs/luci-static/resources/view/homeproxy/server.js:464 msgid "Ping timeout" msgstr "Ping 超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:630 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1060 msgid "Plugin" msgstr "插件" -#: htdocs/luci-static/resources/view/homeproxy/node.js:637 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1067 msgid "Plugin opts" msgstr "插件参数" -#: htdocs/luci-static/resources/view/homeproxy/client.js:848 -#: htdocs/luci-static/resources/view/homeproxy/client.js:962 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1286 -#: htdocs/luci-static/resources/view/homeproxy/node.js:453 +#: htdocs/luci-static/resources/view/homeproxy/client.js:871 +#: htdocs/luci-static/resources/view/homeproxy/client.js:985 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1309 +#: htdocs/luci-static/resources/view/homeproxy/node.js:883 msgid "Port" msgstr "端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:276 +#: htdocs/luci-static/resources/view/homeproxy/client.js:299 msgid "Port %s alrealy exists!" msgstr "端口 %s 已存在!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:582 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/client.js:605 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1085 msgid "Port fields" msgstr "端口字段" -#: htdocs/luci-static/resources/view/homeproxy/node.js:536 +#: htdocs/luci-static/resources/view/homeproxy/node.js:966 msgid "Port hopping interval in seconds." msgstr "端口跳跃间隔(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:853 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1291 +#: htdocs/luci-static/resources/view/homeproxy/client.js:876 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1314 msgid "Port range" msgstr "端口范围" -#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1348 msgid "Pre-shared key" msgstr "预共享密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1150 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 msgid "Predefined" msgstr "预定义" @@ -1812,106 +1941,110 @@ msgstr "优先 IPv4" msgid "Prefer IPv6" msgstr "优先 IPv6" -#: htdocs/luci-static/resources/view/homeproxy/node.js:678 -#: htdocs/luci-static/resources/view/homeproxy/node.js:903 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1108 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1333 msgid "Private key" msgstr "私钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:683 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 msgid "Private key passphrase" msgstr "私钥指纹" -#: htdocs/luci-static/resources/view/homeproxy/client.js:583 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1063 +#: htdocs/luci-static/resources/view/homeproxy/node.js:484 +msgid "Probe failed" +msgstr "探测失败" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:606 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1086 msgid "Process fields" msgstr "进程字段" -#: htdocs/luci-static/resources/view/homeproxy/client.js:858 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1296 +#: htdocs/luci-static/resources/view/homeproxy/client.js:881 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1319 msgid "Process name" msgstr "进程名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:862 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:885 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1323 msgid "Process path" msgstr "进程路径" -#: htdocs/luci-static/resources/view/homeproxy/client.js:866 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1304 +#: htdocs/luci-static/resources/view/homeproxy/client.js:889 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1327 msgid "Process path (regex)" msgstr "进程路径(正则表达式)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:615 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 -#: htdocs/luci-static/resources/view/homeproxy/node.js:543 -#: htdocs/luci-static/resources/view/homeproxy/node.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:638 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1127 +#: htdocs/luci-static/resources/view/homeproxy/node.js:973 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1381 #: htdocs/luci-static/resources/view/homeproxy/server.js:250 msgid "Protocol" msgstr "协议" -#: htdocs/luci-static/resources/view/homeproxy/node.js:767 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1197 msgid "Protocol parameter. Enable length block encryption." msgstr "协议参数。启用长度块加密。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:760 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1190 msgid "" "Protocol parameter. Will waste traffic randomly if enabled (enabled by " "default in v2ray and cannot be disabled)." msgstr "协议参数。 如启用会随机浪费流量(在 v2ray 中默认启用并且无法禁用)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1482 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1505 msgid "Proxy Domain List" msgstr "代理域名列表" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1437 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1466 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1460 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1489 msgid "Proxy IPv4 IP-s" msgstr "代理 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1440 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1469 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1463 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1492 msgid "Proxy IPv6 IP-s" msgstr "代理 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1443 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1466 msgid "Proxy MAC-s" msgstr "代理 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1447 msgid "Proxy all except listed" msgstr "仅允许列表外" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1421 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1444 msgid "Proxy filter mode" msgstr "代理过滤模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1423 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1446 msgid "Proxy listed only" msgstr "仅允许列表内" -#: htdocs/luci-static/resources/view/homeproxy/client.js:284 +#: htdocs/luci-static/resources/view/homeproxy/client.js:307 msgid "Proxy mode" msgstr "代理模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:497 +#: htdocs/luci-static/resources/view/homeproxy/node.js:927 msgid "Proxy protocol" msgstr "代理协议" -#: htdocs/luci-static/resources/view/homeproxy/client.js:621 -#: htdocs/luci-static/resources/view/homeproxy/client.js:953 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1109 -#: htdocs/luci-static/resources/view/homeproxy/node.js:712 -#: htdocs/luci-static/resources/view/homeproxy/node.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:644 +#: htdocs/luci-static/resources/view/homeproxy/client.js:976 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1132 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1209 #: htdocs/luci-static/resources/view/homeproxy/server.js:405 msgid "QUIC" msgstr "QUIC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:699 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1129 #: htdocs/luci-static/resources/view/homeproxy/server.js:355 msgid "QUIC congestion control algorithm." msgstr "QUIC 拥塞控制算法。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:599 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1029 #: htdocs/luci-static/resources/view/homeproxy/server.js:307 msgid "QUIC connection receive window" msgstr "QUIC 连接窗口" @@ -1920,29 +2053,33 @@ msgstr "QUIC 连接窗口" msgid "QUIC maximum concurrent bidirectional streams" msgstr "QUIC 最大双向并发流" -#: htdocs/luci-static/resources/view/homeproxy/node.js:593 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1023 #: htdocs/luci-static/resources/view/homeproxy/server.js:300 msgid "QUIC stream receive window" msgstr "QUIC 流接收窗口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1095 +#: htdocs/luci-static/resources/view/homeproxy/client.js:209 +msgid "Quad9 Public DNS (DoH)" +msgstr "Quad9 公共DNS (DoH)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1118 msgid "Query type" msgstr "请求类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1215 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1238 msgid "RCode" msgstr "RCode" -#: htdocs/luci-static/resources/view/homeproxy/client.js:622 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1110 +#: htdocs/luci-static/resources/view/homeproxy/client.js:645 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 msgid "RDP" msgstr "RDP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:917 +#: htdocs/luci-static/resources/view/homeproxy/client.js:940 msgid "RDRC timeout" msgstr "RDRC 超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1572 #: htdocs/luci-static/resources/view/homeproxy/server.js:709 msgid "REALITY" msgstr "REALITY" @@ -1951,42 +2088,50 @@ msgstr "REALITY" msgid "REALITY private key" msgstr "REALITY 私钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1147 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1577 #: htdocs/luci-static/resources/view/homeproxy/server.js:730 msgid "REALITY public key" msgstr "REALITY 公钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1153 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1583 #: htdocs/luci-static/resources/view/homeproxy/server.js:734 msgid "REALITY short ID" msgstr "REALITY 标识符" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1785 +msgid "RP" +msgstr "RP" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:486 +msgid "RPC failed" +msgstr "RPC 失败" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:63 #: htdocs/luci-static/resources/view/homeproxy/server.js:58 msgid "RUNNING" msgstr "运行中" -#: htdocs/luci-static/resources/view/homeproxy/node.js:665 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1095 msgid "Random version will be used if empty." msgstr "如留空,则使用随机版本。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:488 +#: htdocs/luci-static/resources/view/homeproxy/client.js:511 msgid "Recursive outbound detected!" msgstr "检测到递归出站!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1013 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1036 msgid "Recursive resolver detected!" msgstr "检测到递归解析器!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:285 +#: htdocs/luci-static/resources/view/homeproxy/client.js:308 msgid "Redirect TCP" msgstr "Redirect TCP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:287 +#: htdocs/luci-static/resources/view/homeproxy/client.js:310 msgid "Redirect TCP + TProxy UDP" msgstr "Redirect TCP + TProxy UDP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:289 +#: htdocs/luci-static/resources/view/homeproxy/client.js:312 msgid "Redirect TCP + Tun UDP" msgstr "Redirect TCP + Tun UDP" @@ -1998,40 +2143,40 @@ msgstr "每 %s 秒刷新。" msgid "Region ID" msgstr "区域 ID" -#: htdocs/luci-static/resources/view/homeproxy/client.js:672 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:695 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1172 msgid "Reject" msgstr "拒绝" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1336 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1359 msgid "Remote" msgstr "远程" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1434 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1874 msgid "Remove %s nodes" msgstr "移除 %s 个节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1864 msgid "Remove all nodes from subscriptions" msgstr "移除所有订阅节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1203 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1226 msgid "Reply with REFUSED" msgstr "回复 REFUSED" -#: htdocs/luci-static/resources/view/homeproxy/client.js:769 +#: htdocs/luci-static/resources/view/homeproxy/client.js:792 msgid "Reply with TCP RST / ICMP port unreachable" msgstr "回复 TCP RST / ICMP 端口不可达" -#: htdocs/luci-static/resources/view/homeproxy/node.js:925 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1355 msgid "Reserved field bytes" msgstr "保留字段字节" -#: htdocs/luci-static/resources/view/homeproxy/client.js:673 +#: htdocs/luci-static/resources/view/homeproxy/client.js:696 msgid "Resolve" msgstr "解析" -#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:803 msgid "Resolve strategy" msgstr "解析策略" @@ -2047,98 +2192,137 @@ msgstr "复用地址" msgid "Reuse listener address." msgstr "复用监听地址。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:792 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1187 +#: htdocs/luci-static/resources/view/homeproxy/client.js:815 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1210 msgid "Rewrite TTL" msgstr "重写 TTL" -#: htdocs/luci-static/resources/view/homeproxy/client.js:793 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1188 +#: htdocs/luci-static/resources/view/homeproxy/client.js:816 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 msgid "Rewrite TTL in DNS responses." msgstr "在 DNS 响应中重写 TTL。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:670 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1147 +#: htdocs/luci-static/resources/view/homeproxy/client.js:693 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1170 msgid "Route" msgstr "路由" -#: htdocs/luci-static/resources/view/homeproxy/client.js:671 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1148 +#: htdocs/luci-static/resources/view/homeproxy/client.js:694 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1171 msgid "Route options" msgstr "路由选项" -#: htdocs/luci-static/resources/view/homeproxy/client.js:396 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Routing Nodes" -msgstr "路由节点" +msgstr "策略组" -#: htdocs/luci-static/resources/view/homeproxy/client.js:567 +#: htdocs/luci-static/resources/view/homeproxy/client.js:590 msgid "Routing Rules" msgstr "路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:120 +#: htdocs/luci-static/resources/view/homeproxy/client.js:145 msgid "Routing Settings" msgstr "路由设置" -#: htdocs/luci-static/resources/view/homeproxy/client.js:251 +#: htdocs/luci-static/resources/view/homeproxy/client.js:274 msgid "Routing mode" msgstr "路由模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:405 +#: htdocs/luci-static/resources/view/homeproxy/client.js:428 msgid "Routing node" -msgstr "路由节点" +msgstr "策略组" -#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:287 msgid "Routing ports" msgstr "路由端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:576 +#: htdocs/luci-static/resources/view/homeproxy/client.js:599 msgid "Routing rule" msgstr "路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1311 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1334 msgid "Rule Set" msgstr "规则集" -#: htdocs/luci-static/resources/view/homeproxy/client.js:646 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1119 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1320 +#: htdocs/luci-static/resources/view/homeproxy/client.js:669 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1343 msgid "Rule set" msgstr "规则集" -#: htdocs/luci-static/resources/view/homeproxy/client.js:661 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1134 +#: htdocs/luci-static/resources/view/homeproxy/client.js:684 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1157 msgid "Rule set IP CIDR as source IP" msgstr "规则集 IP CIDR 作为源 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1353 +msgid "Tag" +msgstr "标签" + +msgid "Used by sing-box rule sets. Please fill it in yourself." +msgstr "用于 sing-box 规则集,请自行填写。" + +msgid "valid tag" +msgstr "有效标签" + +msgid "valid duration" +msgstr "有效时间格式" + +msgid "valid cron expression" +msgstr "有效 cron 表达式" + +msgid "Manual select" +msgstr "手动选择" + +msgid "Custom nodes" +msgstr "自选节点" + +msgid "Custom" +msgstr "自定义" + +msgid "Custom URL" +msgstr "自定义URL" + +msgid "Policy nodes" +msgstr "策略节点" + +msgid "List of policy groups." +msgstr "策略组列表。" + +msgid "Subscription nodes" +msgstr "订阅节点" + +msgid "List of nodes from subscriptions." +msgstr "订阅节点列表。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1376 msgid "Rule set URL" msgstr "规则集 URL" -#: htdocs/luci-static/resources/view/homeproxy/client.js:623 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1111 -#: htdocs/luci-static/resources/view/homeproxy/node.js:438 +#: htdocs/luci-static/resources/view/homeproxy/client.js:646 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1134 +#: htdocs/luci-static/resources/view/homeproxy/node.js:868 msgid "SSH" msgstr "SSH" -#: htdocs/luci-static/resources/view/homeproxy/client.js:624 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 +#: htdocs/luci-static/resources/view/homeproxy/client.js:647 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 msgid "STUN" msgstr "STUN" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1177 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1607 msgid "SUoT version" msgstr "SUoT 版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:632 +#: htdocs/luci-static/resources/view/homeproxy/client.js:655 msgid "Safari / Apple Network API" msgstr "Safari / Apple Network API" -#: htdocs/luci-static/resources/view/homeproxy/node.js:569 +#: htdocs/luci-static/resources/view/homeproxy/node.js:999 #: htdocs/luci-static/resources/view/homeproxy/server.js:290 msgid "Salamander" msgstr "Salamander" -#: htdocs/luci-static/resources/view/homeproxy/client.js:152 +#: htdocs/luci-static/resources/view/homeproxy/client.js:177 msgid "Same as main node" msgstr "保持与主节点一致" @@ -2147,15 +2331,15 @@ msgstr "保持与主节点一致" msgid "Save" msgstr "保存" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1397 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1837 msgid "Save current settings" msgstr "保存当前设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1394 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1834 msgid "Save subscriptions settings" msgstr "保存订阅设置" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1155 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1178 #: htdocs/luci-static/resources/view/homeproxy/server.js:156 msgid "Server" msgstr "服务器" @@ -2178,29 +2362,29 @@ msgstr "服务器设置" msgid "Service Status" msgstr "服务状态" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1175 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1198 msgid "Set domain strategy for this query." msgstr "为此查询设置域名策略。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:436 +#: htdocs/luci-static/resources/view/homeproxy/node.js:866 msgid "ShadowTLS" msgstr "ShadowTLS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1074 msgid "ShadowTLS version" msgstr "ShadowTLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:435 +#: htdocs/luci-static/resources/view/homeproxy/node.js:865 #: htdocs/luci-static/resources/view/homeproxy/server.js:184 msgid "Shadowsocks" msgstr "Shadowsocks" -#: htdocs/luci-static/resources/view/homeproxy/client.js:628 +#: htdocs/luci-static/resources/view/homeproxy/client.js:651 msgid "Sniffed client type (QUIC client type or SSH client name)." msgstr "嗅探到的客户端类型(QUIC 客户端类型或 SSH 客户端名称)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:616 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1105 +#: htdocs/luci-static/resources/view/homeproxy/client.js:639 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1128 msgid "" "Sniffed protocol, see Sniff for details." @@ -2208,53 +2392,53 @@ msgstr "" "嗅探协议,具体参见 Sniff。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:867 #: htdocs/luci-static/resources/view/homeproxy/server.js:185 msgid "Socks" msgstr "Socks" -#: htdocs/luci-static/resources/view/homeproxy/node.js:654 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1084 msgid "Socks version" msgstr "Socks 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:655 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1085 msgid "Socks4" msgstr "Socks4" -#: htdocs/luci-static/resources/view/homeproxy/node.js:656 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 msgid "Socks4A" msgstr "Socks4A" -#: htdocs/luci-static/resources/view/homeproxy/node.js:657 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1087 msgid "Socks5" msgstr "Socks5" -#: htdocs/luci-static/resources/view/homeproxy/client.js:822 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1259 +#: htdocs/luci-static/resources/view/homeproxy/client.js:845 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1282 msgid "Source IP CIDR" msgstr "源 IP CIDR" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1342 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 msgid "Source file" msgstr "源文件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:838 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1276 +#: htdocs/luci-static/resources/view/homeproxy/client.js:861 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1299 msgid "Source port" msgstr "源端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:843 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1281 +#: htdocs/luci-static/resources/view/homeproxy/client.js:866 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1304 msgid "Source port range" msgstr "源端口范围" -#: htdocs/luci-static/resources/view/homeproxy/client.js:750 +#: htdocs/luci-static/resources/view/homeproxy/client.js:773 msgid "" "Specifies DNS server tag to use instead of selecting through DNS routing." msgstr "指定使用的 DNS 服务器,而不是通过 DNS 规则选择。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:796 -#: htdocs/luci-static/resources/view/homeproxy/node.js:848 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1278 msgid "" "Specifies the period of time (in seconds) after which a health check will be " "performed using a ping frame if no frames have been received on the " @@ -2275,8 +2459,8 @@ msgstr "" "指定闲置客户端应在多长时间(单位:秒)内使用 GOAWAY 帧关闭。PING 帧不被视为活" "动。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:800 -#: htdocs/luci-static/resources/view/homeproxy/node.js:856 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1230 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1286 msgid "" "Specifies the timeout duration (in seconds) after sending a PING frame, " "within which a response must be received.
If a response to the PING " @@ -2286,42 +2470,34 @@ msgstr "" "指定发送 PING 帧后,在指定的超时时间(单位:秒)内必须接收到响应。
如果在" "指定的超时时间内没有收到 PING 帧的响应,则连接将关闭。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:265 +#: htdocs/luci-static/resources/view/homeproxy/client.js:288 msgid "" "Specify target ports to be proxied. Multiple ports must be separated by " "commas." msgstr "指定需要被代理的目标端口。多个端口必须用逗号隔开。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:913 -msgid "Store RDRC" -msgstr "存储 RDRC" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:914 -msgid "" -"Store rejected DNS response cache.
The check results of Address " -"filter DNS rule items will be cached until expiration." -msgstr "" -"存储被拒绝的 DNS 响应缓存。
地址过滤 DNS 规则 的检查结果将被" -"缓存直到过期。" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:557 +#: htdocs/luci-static/resources/view/homeproxy/node.js:987 #: htdocs/luci-static/resources/view/homeproxy/server.js:278 msgid "String" msgstr "字符串" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1322 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1755 msgid "Sub (%s)" msgstr "订阅(%s)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1349 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1789 msgid "Subscription URL-s" msgstr "订阅地址" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1333 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1766 msgid "Subscriptions" msgstr "订阅" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1273 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +msgid "Success" +msgstr "成功" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1706 msgid "Successfully imported %s nodes of total %s." msgstr "成功导入 %s 个节点,共 %s 个。" @@ -2329,8 +2505,8 @@ msgstr "成功导入 %s 个节点,共 %s 个。" msgid "Successfully updated." msgstr "更新成功。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1233 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1666 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1790 msgid "" "Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) " "online configuration delivery standard." @@ -2338,98 +2514,94 @@ msgstr "" "支持 Hysteria、Shadowsocks、Trojan、v2rayN(VMess)和 XTLS(VLESS)在线配置交" "付标准。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:180 +#: htdocs/luci-static/resources/view/homeproxy/client.js:205 msgid "" "Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified." msgstr "支持 UDP、TCP、DoH、DoQ、DoT。如未指定则使用 TCP 协议。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:313 +#: htdocs/luci-static/resources/view/homeproxy/client.js:336 msgid "System" msgstr "系统" -#: htdocs/luci-static/resources/view/homeproxy/client.js:383 -#: htdocs/luci-static/resources/view/homeproxy/client.js:435 -#: htdocs/luci-static/resources/view/homeproxy/client.js:757 -#: htdocs/luci-static/resources/view/homeproxy/client.js:888 -#: htdocs/luci-static/resources/view/homeproxy/client.js:996 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/client.js:406 +#: htdocs/luci-static/resources/view/homeproxy/client.js:458 +#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:911 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1019 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1185 msgid "System DNS" msgstr "系统 DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:638 -#: htdocs/luci-static/resources/view/homeproxy/client.js:949 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1100 +#: htdocs/luci-static/resources/view/homeproxy/client.js:661 +#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1123 #: htdocs/luci-static/resources/view/homeproxy/server.js:867 msgid "TCP" msgstr "TCP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1161 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1591 #: htdocs/luci-static/resources/view/homeproxy/server.js:845 msgid "TCP fast open" msgstr "TCP 快速打开" -#: htdocs/luci-static/resources/view/homeproxy/client.js:307 +#: htdocs/luci-static/resources/view/homeproxy/client.js:330 msgid "TCP/IP stack" msgstr "TCP/IP 协议栈" -#: htdocs/luci-static/resources/view/homeproxy/client.js:308 +#: htdocs/luci-static/resources/view/homeproxy/client.js:331 msgid "TCP/IP stack." msgstr "TCP/IP 协议栈。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:625 -#: htdocs/luci-static/resources/view/homeproxy/client.js:950 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1003 +#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:973 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1136 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1433 #: htdocs/luci-static/resources/view/homeproxy/server.js:532 msgid "TLS" msgstr "TLS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1035 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1465 #: htdocs/luci-static/resources/view/homeproxy/server.js:565 msgid "TLS ALPN" msgstr "TLS ALPN" -#: htdocs/luci-static/resources/view/homeproxy/client.js:980 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1030 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1003 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1460 #: htdocs/luci-static/resources/view/homeproxy/server.js:560 msgid "TLS SNI" msgstr "TLS SNI" -#: htdocs/luci-static/resources/view/homeproxy/client.js:735 +#: htdocs/luci-static/resources/view/homeproxy/client.js:758 msgid "TLS fragment" msgstr "TLS 分片" -#: htdocs/luci-static/resources/view/homeproxy/node.js:787 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1217 #: htdocs/luci-static/resources/view/homeproxy/server.js:413 msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used." msgstr "不强制执行 TLS。如未配置 TLS,将使用纯 HTTP 1.1。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:729 -#: htdocs/luci-static/resources/view/homeproxy/client.js:737 +#: htdocs/luci-static/resources/view/homeproxy/client.js:752 +#: htdocs/luci-static/resources/view/homeproxy/client.js:760 msgid "TLS record fragment" msgstr "TLS 记录分片" -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 -msgid "" -"Tag of a another server to resolve the domain name in the address. Required " -"if address contains domain." -msgstr "" -"用于解析本 DNS 服务器的域名的另一个 DNS 服务器的标签。如果服务器地址包括域名" -"则必须。" +#: htdocs/luci-static/resources/view/homeproxy/client.js:1012 +msgid "Used to resolve DNS server addresses in domain form. IP addresses do not require it." +msgstr "用于解析域名形式的DNS,如:https://dns.alidns.com/dns-query,数字形式则不需要如:https://223.5.5.5/dns-query" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1028 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1051 msgid "Tag of an outbound for connecting to the dns server." msgstr "用于连接到 DNS 服务器的出站标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1376 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1399 msgid "Tag of the outbound to download rule set." msgstr "用于下载规则集的出站标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1156 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1179 msgid "Tag of the target dns server." msgstr "目标 DNS 服务器标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:702 msgid "Tag of the target outbound." msgstr "目标出站标签。" @@ -2438,56 +2610,74 @@ msgid "" "Tell the client to use the BBR flow control algorithm instead of Hysteria CC." msgstr "让客户端使用 BBR 流控算法。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:187 -#: htdocs/luci-static/resources/view/homeproxy/client.js:222 -msgid "Tencent Public DNS (119.29.29.29)" -msgstr "腾讯公共 DNS(119.29.29.29)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:245 +msgid "Tencent Public DNS (DoH)" +msgstr "腾讯 公共DNS (DoH)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:510 +#: htdocs/luci-static/resources/view/homeproxy/node.js:541 +#: htdocs/luci-static/resources/view/homeproxy/node.js:826 +msgid "Test" +msgstr "测试" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1924 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1928 +msgid "Test Current List" +msgstr "测试当前列表" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:533 msgid "Test URL" msgstr "测试 URL" -#: htdocs/luci-static/resources/view/homeproxy/client.js:138 -#: htdocs/luci-static/resources/view/homeproxy/client.js:167 -#: htdocs/luci-static/resources/view/homeproxy/client.js:530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:163 +#: htdocs/luci-static/resources/view/homeproxy/client.js:192 +#: htdocs/luci-static/resources/view/homeproxy/client.js:553 msgid "Test interval" msgstr "测试间隔" -#: htdocs/luci-static/resources/view/homeproxy/client.js:538 +#: htdocs/luci-static/resources/view/homeproxy/client.js:561 msgid "Test interval must be less or equal than idle timeout." msgstr "测试间隔时间必须小于或等于空闲超时时间。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:144 -#: htdocs/luci-static/resources/view/homeproxy/client.js:173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:546 +#: htdocs/luci-static/resources/view/homeproxy/client.js:169 +#: htdocs/luci-static/resources/view/homeproxy/client.js:198 +#: htdocs/luci-static/resources/view/homeproxy/client.js:569 msgid "Test tolerance" msgstr "测试容差" +#: htdocs/luci-static/resources/view/homeproxy/node.js:498 +#: htdocs/luci-static/resources/view/homeproxy/node.js:541 +msgid "Testing" +msgstr "测试中" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1917 +msgid "Testing Current List..." +msgstr "当前列表测试中..." + #: htdocs/luci-static/resources/view/homeproxy/server.js:628 msgid "The ACME CA provider to use." msgstr "使用的 ACME CA 颁发机构。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:878 +#: htdocs/luci-static/resources/view/homeproxy/client.js:901 msgid "The DNS strategy for resolving the domain name in the address." msgstr "解析域名的默认策略。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:600 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1030 #: htdocs/luci-static/resources/view/homeproxy/server.js:308 msgid "The QUIC connection-level flow control window for receiving data." msgstr "用于接收数据的 QUIC 连接级流控制窗口。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:594 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1024 #: htdocs/luci-static/resources/view/homeproxy/server.js:301 msgid "The QUIC stream-level flow control window for receiving data." msgstr "用于接收数据的 QUIC 流级流控制窗口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:511 +#: htdocs/luci-static/resources/view/homeproxy/client.js:534 msgid "The URL to test." msgstr "用于测试的 URL。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:958 -msgid "The address of the dns server." -msgstr "DNS 服务器的地址。" +#: htdocs/luci-static/resources/view/homeproxy/client.js:981 +msgid "Full URL, e.g. https://dns.alidns.com/dns-query" +msgstr "完整路径 如:https://dns.alidns.com/dns-query" #: htdocs/luci-static/resources/view/homeproxy/server.js:679 msgid "" @@ -2504,49 +2694,21 @@ msgid "" msgstr "" "用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:596 -msgid "" -"The default rule uses the following matching logic:
(domain || " -"domain_suffix || domain_keyword || domain_regex || ip_cidr || " -"ip_is_private) &&
(port || port_range) &&
(source_ip_cidr || source_ip_is_private) &&
(source_port || source_port_range) &&
other fields.
Additionally, included rule sets can be considered merged rather " -"than as a single rule sub-item." -msgstr "" -"默认规则使用以下匹配逻辑:
(domain || domain_suffix || " -"domain_keyword || domain_regex || ip_cidr || ip_is_private) &&
(port || port_range) &&
(source_ip_cidr || " -"source_ip_is_private) &&
(source_port || " -"source_port_range) &&
其他字段。此外,包含的所有规则" -"集会被合并而不是独立生效。" +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "In default mode, rule fields are matched by category. Any condition in the same category can match, while different categories must match at the same time. Rule sets are merged into the rule for matching and are not treated as separate sub-rules." +msgstr "默认模式下,规则会按条件类别进行匹配。同一类别内任意条件命中即可,不同类别之间需要同时满足。规则集会合并到当前规则中参与匹配,不会作为独立子规则单独生效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1076 -msgid "" -"The default rule uses the following matching logic:
(domain || " -"domain_suffix || domain_keyword || domain_regex) &&
(port " -"|| port_range) &&
(source_ip_cidr || source_ip_is_private) &&
(source_port || source_port_range) &&
other fields.
Additionally, included rule sets can be " -"considered merged rather than as a single rule sub-item." -msgstr "" -"默认规则使用以下匹配逻辑:
(domain || domain_suffix || " -"domain_keyword || domain_regex) &&
(port || port_range) &&
(source_ip_cidr || source_ip_is_private) &&
(source_port || source_port_range) &&
其他字段。此外,包含的所有规则集会被合并而不是独立生效。" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:218 +#: htdocs/luci-static/resources/view/homeproxy/client.js:242 msgid "" "The dns server for resolving China domains. Support UDP, TCP, DoH, DoQ, DoT." msgstr "用于解析国内域名的 DNS 服务器。支持 UDP、TCP、DoH、DoQ、DoT。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:447 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1021 +#: htdocs/luci-static/resources/view/homeproxy/client.js:470 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1044 msgid "The domain strategy for resolving the domain name in the address." msgstr "用于解析本 DNS 服务器的域名的策略。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1065 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1495 #: htdocs/luci-static/resources/view/homeproxy/server.js:587 msgid "" "The elliptic curves that will be used in an ECDHE handshake, in preference " @@ -2559,17 +2721,17 @@ msgid "" "account." msgstr "创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:743 +#: htdocs/luci-static/resources/view/homeproxy/client.js:766 msgid "" "The fallback value in milliseconds used when TLS segmentation cannot " "automatically determine the wait time." msgstr "当 TLS 分段无法自动确定等待时间时使用的回退值(单位:毫秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:554 +#: htdocs/luci-static/resources/view/homeproxy/client.js:577 msgid "The idle timeout in seconds." msgstr "空闲超时时间(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1057 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1487 #: htdocs/luci-static/resources/view/homeproxy/server.js:579 msgid "The maximum TLS version that is acceptable." msgstr "可接受的最高 TLS 版本。" @@ -2584,32 +2746,28 @@ msgstr "允许对等点打开的 QUIC 并发双向流的最大数量。" msgid "The maximum time difference between the server and the client." msgstr "服务器和客户端之间的最大时间差。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1049 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1479 #: htdocs/luci-static/resources/view/homeproxy/server.js:571 msgid "The minimum TLS version that is acceptable." msgstr "可接受的最低 TLS 版本。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:102 +#: htdocs/luci-static/resources/view/homeproxy/client.js:113 #: htdocs/luci-static/resources/view/homeproxy/server.js:130 -msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64." -msgstr "为 ARM64/AMD64 设计的现代 ImmortalWrt 代理平台。" +msgid "OpenWrt-designed Sing-box proxy management platform. It is recommended to disable Dnsmasq DNS redirection." +msgstr "Openwrt设计的Sing-box代理管理平台,建议Dnsmasq的DNS重定向去勾。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:477 #: htdocs/luci-static/resources/view/homeproxy/server.js:875 msgid "The network interface to bind to." msgstr "绑定到的网络接口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:968 -msgid "The path of the DNS server." -msgstr "DNS 服务器的路径。" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1099 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1529 msgid "" "The path to the ECH config, in PEM format. If empty, load from DNS will be " "attempted." msgstr "PEM 格式的 ECH 配置路径。如果为空,将尝试从 DNS 加载。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1078 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1508 msgid "The path to the server certificate, in PEM format." msgstr "服务端证书路径,需要 PEM 格式。" @@ -2617,11 +2775,11 @@ msgstr "服务端证书路径,需要 PEM 格式。" msgid "The port must be unique." msgstr "必须是唯一端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:963 -msgid "The port of the DNS server." -msgstr "DNS 服务器的端口。" +#: htdocs/luci-static/resources/view/homeproxy/client.js:986 +msgid "Leave empty to use the default port." +msgstr "留空则表示使用默认端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1216 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1239 msgid "The response code." msgstr "响应代码。" @@ -2633,25 +2791,25 @@ msgstr "服务端私钥,需要 PEM 格式。" msgid "The server public key, in PEM format." msgstr "服务端公钥,需要 PEM 格式。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:461 +#: htdocs/luci-static/resources/view/homeproxy/client.js:484 msgid "" "The tag of the upstream outbound.
Other dial fields will be ignored when " "enabled." msgstr "上游出站的标签。
启用时,其他拨号字段将被忽略。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:139 -#: htdocs/luci-static/resources/view/homeproxy/client.js:168 -#: htdocs/luci-static/resources/view/homeproxy/client.js:531 +#: htdocs/luci-static/resources/view/homeproxy/client.js:164 +#: htdocs/luci-static/resources/view/homeproxy/client.js:193 +#: htdocs/luci-static/resources/view/homeproxy/client.js:554 msgid "The test interval in seconds." msgstr "测试间隔时间(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:145 -#: htdocs/luci-static/resources/view/homeproxy/client.js:174 -#: htdocs/luci-static/resources/view/homeproxy/client.js:547 +#: htdocs/luci-static/resources/view/homeproxy/client.js:170 +#: htdocs/luci-static/resources/view/homeproxy/client.js:199 +#: htdocs/luci-static/resources/view/homeproxy/client.js:570 msgid "The test tolerance in milliseconds." msgstr "测试容差时间(单位:毫秒)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:807 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1237 #: htdocs/luci-static/resources/view/homeproxy/server.js:465 msgid "" "The timeout (in seconds) that after performing a keepalive check, the client " @@ -2661,15 +2819,15 @@ msgstr "" "经过一段时间(单位:秒)之后,客户端将执行 keepalive 检查并等待活动。如果没有" "检测到任何活动,则会关闭连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1043 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1473 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1825 msgid "" "This is DANGEROUS, your traffic is almost like " "PLAIN TEXT! Use at your own risk!" msgstr "" "这是危险行为,您的流量将几乎等同于明文!使用风险自负!" -#: htdocs/luci-static/resources/view/homeproxy/node.js:717 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1147 msgid "" "This is the TUIC port of the UDP over TCP protocol, designed to provide a " "QUIC stream based UDP relay mode that TUIC does not provide." @@ -2677,18 +2835,21 @@ msgstr "" "这是 TUIC 的 UDP over TCP 协议移植, 旨在提供 TUIC 不提供的基于 QUIC 流的 " "UDP 中继模式。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:188 -#: htdocs/luci-static/resources/view/homeproxy/client.js:223 -msgid "ThreatBook Public DNS (117.50.10.10)" -msgstr "微步在线公共 DNS(117.50.10.10)" +#: htdocs/luci-static/resources/view/homeproxy/client.js:244 +msgid "ThreatBook Public DNS (DoH)" +msgstr "微步 公共DNS (DoH)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:723 +#: htdocs/luci-static/resources/view/homeproxy/node.js:502 +msgid "Timeout" +msgstr "超时" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:746 msgid "" "Timeout for UDP connections.
Setting a larger value than the UDP timeout " "in inbounds will have no effect." msgstr "UDP 连接的超时时间。
设置比入站的 UDP 超时更大的值将无效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:918 +#: htdocs/luci-static/resources/view/homeproxy/client.js:941 msgid "" "Timeout of rejected DNS response cache in seconds. 604800 (7d) " "is used by default." @@ -2703,7 +2864,7 @@ msgid "" msgstr "" "要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:292 +#: htdocs/luci-static/resources/view/homeproxy/client.js:315 msgid "" "To enable Tun support, you need to install ip-full and " "kmod-tun" @@ -2714,82 +2875,98 @@ msgstr "" msgid "Trace" msgstr "跟踪" -#: htdocs/luci-static/resources/view/homeproxy/node.js:773 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1203 #: htdocs/luci-static/resources/view/homeproxy/server.js:399 msgid "Transport" msgstr "传输层" -#: htdocs/luci-static/resources/view/homeproxy/node.js:439 +#: htdocs/luci-static/resources/view/homeproxy/node.js:869 #: htdocs/luci-static/resources/view/homeproxy/server.js:186 msgid "Trojan" msgstr "Trojan" -#: htdocs/luci-static/resources/view/homeproxy/node.js:441 +#: htdocs/luci-static/resources/view/homeproxy/node.js:871 #: htdocs/luci-static/resources/view/homeproxy/server.js:188 msgid "Tuic" msgstr "Tuic" -#: htdocs/luci-static/resources/view/homeproxy/client.js:290 +#: htdocs/luci-static/resources/view/homeproxy/client.js:313 msgid "Tun TCP/UDP" msgstr "Tun TCP/UDP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:947 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1334 -#: htdocs/luci-static/resources/view/homeproxy/node.js:427 +#: htdocs/luci-static/resources/view/homeproxy/client.js:970 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 +#: htdocs/luci-static/resources/view/homeproxy/node.js:857 #: htdocs/luci-static/resources/view/homeproxy/server.js:175 msgid "Type" msgstr "类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:639 -#: htdocs/luci-static/resources/view/homeproxy/client.js:948 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1101 +msgid "Type error" +msgstr "类型错误" + +msgid "TypeError" +msgstr "类型错误" + +msgid "Cannot convert null to object." +msgstr "无法将 null 转换为对象。" + +msgid "can't convert null to object" +msgstr "无法将 null 转换为对象" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:662 +#: htdocs/luci-static/resources/view/homeproxy/client.js:971 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1124 #: htdocs/luci-static/resources/view/homeproxy/server.js:868 msgid "UDP" msgstr "UDP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1167 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1597 #: htdocs/luci-static/resources/view/homeproxy/server.js:854 msgid "UDP Fragment" msgstr "UDP 分片" -#: htdocs/luci-static/resources/view/homeproxy/client.js:335 +#: htdocs/luci-static/resources/view/homeproxy/client.js:358 #: htdocs/luci-static/resources/view/homeproxy/server.js:859 msgid "UDP NAT expiration time" msgstr "UDP NAT 过期时间" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1171 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1601 msgid "UDP over TCP" msgstr "UDP over TCP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:716 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1146 msgid "UDP over stream" msgstr "UDP over stream" -#: htdocs/luci-static/resources/view/homeproxy/node.js:709 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1139 msgid "UDP packet relay mode." msgstr "UDP 包中继模式。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:708 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1138 msgid "UDP relay mode" msgstr "UDP 中继模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:722 +#: htdocs/luci-static/resources/view/homeproxy/client.js:745 msgid "UDP timeout" msgstr "UDP 超时" -#: htdocs/luci-static/resources/view/homeproxy/client.js:124 -#: htdocs/luci-static/resources/view/homeproxy/client.js:153 -#: htdocs/luci-static/resources/view/homeproxy/client.js:421 +#: htdocs/luci-static/resources/view/homeproxy/client.js:149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:444 msgid "URLTest" msgstr "URLTest" -#: htdocs/luci-static/resources/view/homeproxy/client.js:131 -#: htdocs/luci-static/resources/view/homeproxy/client.js:160 -#: htdocs/luci-static/resources/view/homeproxy/client.js:496 +#: htdocs/luci-static/resources/view/homeproxy/client.js:156 +#: htdocs/luci-static/resources/view/homeproxy/client.js:185 +#: htdocs/luci-static/resources/view/homeproxy/client.js:519 msgid "URLTest nodes" msgstr "URLTest 节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:690 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 +msgid "URLTest: %s" +msgstr "URLTest: %s" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1120 #: htdocs/luci-static/resources/view/homeproxy/server.js:346 msgid "UUID" msgstr "UUID" @@ -2802,11 +2979,11 @@ msgstr "未知错误。" msgid "Unknown error: %s" msgstr "未知错误:%s" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1565 msgid "Unsupported fingerprint!" msgstr "不支持的指纹!" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1409 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1849 msgid "Update %s subscriptions" msgstr "更新 %s 个订阅" @@ -2814,45 +2991,77 @@ msgstr "更新 %s 个订阅" msgid "Update failed." msgstr "更新失败。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1392 -msgid "Update interval" -msgstr "更新间隔" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:1393 -msgid "Update interval of rule set." -msgstr "规则集更新间隔。" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1404 -msgid "Update nodes from subscriptions" -msgstr "从订阅更新节点" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1346 -msgid "Update subscriptions via proxy." -msgstr "使用代理更新订阅。" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1339 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1415 msgid "Update time" msgstr "更新时间" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1345 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1416 +msgid "Update time (daily)" +msgstr "更新时间(每天)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1417 +msgid "Update time (weekly)" +msgstr "更新时间(每周)" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1418 +msgid "Every day" +msgstr "每天" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1419 +msgid "Every Monday" +msgstr "每周一" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1420 +msgid "Every Tuesday" +msgstr "每周二" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1421 +msgid "Every Wednesday" +msgstr "每周三" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1422 +msgid "Every Thursday" +msgstr "每周四" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1423 +msgid "Every Friday" +msgstr "每周五" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1424 +msgid "Every Saturday" +msgstr "每周六" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1425 +msgid "Every Sunday" +msgstr "每周日" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1844 +msgid "Update nodes from subscriptions" +msgstr "从订阅更新节点" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1779 +msgid "Update subscriptions via proxy." +msgstr "使用代理更新订阅。" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1778 msgid "Update via proxy" msgstr "使用代理更新" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1104 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1534 msgid "Upload ECH config" msgstr "上传 ECH 配置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:995 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1425 #: htdocs/luci-static/resources/view/homeproxy/server.js:523 msgid "Upload bandwidth" msgstr "上传带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:996 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1426 #: htdocs/luci-static/resources/view/homeproxy/server.js:524 msgid "Upload bandwidth in Mbps." msgstr "上传带宽(单位:Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1085 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1515 #: htdocs/luci-static/resources/view/homeproxy/server.js:767 msgid "Upload certificate" msgstr "上传证书" @@ -2861,8 +3070,8 @@ msgstr "上传证书" msgid "Upload key" msgstr "上传密钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1088 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1107 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1518 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1537 #: htdocs/luci-static/resources/view/homeproxy/server.js:770 #: htdocs/luci-static/resources/view/homeproxy/server.js:789 msgid "Upload..." @@ -2872,47 +3081,47 @@ msgstr "上传..." msgid "Use ACME TLS certificate issuer." msgstr "使用 ACME TLS 证书颁发机构。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1031 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1461 #: htdocs/luci-static/resources/view/homeproxy/server.js:561 msgid "" "Used to verify the hostname on the returned certificates unless insecure is " "given." msgstr "用于验证返回证书上的主机名。如允许不安全连接,此配置无效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:981 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 msgid "Used to verify the hostname on the returned certificates." msgstr "用于验证返回证书上的主机名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:642 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:665 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 msgid "User" msgstr "用户" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1379 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1819 msgid "User-Agent" msgstr "用户代理" -#: htdocs/luci-static/resources/view/homeproxy/node.js:458 +#: htdocs/luci-static/resources/view/homeproxy/node.js:888 #: htdocs/luci-static/resources/view/homeproxy/server.js:203 msgid "Username" msgstr "用户名" -#: htdocs/luci-static/resources/view/homeproxy/node.js:444 +#: htdocs/luci-static/resources/view/homeproxy/node.js:874 #: htdocs/luci-static/resources/view/homeproxy/server.js:189 msgid "VLESS" msgstr "VLESS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:875 #: htdocs/luci-static/resources/view/homeproxy/server.js:190 msgid "VMess" msgstr "VMess" -#: htdocs/luci-static/resources/view/homeproxy/client.js:181 -#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:243 msgid "WAN DNS (read from interface)" msgstr "WAN DNS(从接口获取)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1464 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1487 msgid "WAN IP Policy" msgstr "WAN IP 策略" @@ -2920,37 +3129,37 @@ msgstr "WAN IP 策略" msgid "Warn" msgstr "警告" -#: htdocs/luci-static/resources/view/homeproxy/node.js:780 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1210 #: htdocs/luci-static/resources/view/homeproxy/server.js:406 msgid "WebSocket" msgstr "WebSocket" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1370 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1810 msgid "Whitelist mode" msgstr "白名单模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:443 +#: htdocs/luci-static/resources/view/homeproxy/node.js:873 msgid "WireGuard" msgstr "WireGuard" -#: htdocs/luci-static/resources/view/homeproxy/node.js:912 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1342 msgid "WireGuard peer public key." msgstr "WireGuard 对端公钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:919 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1349 msgid "WireGuard pre-shared key." msgstr "WireGuard 预共享密钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:904 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1334 msgid "WireGuard requires base64-encoded private keys." msgstr "WireGuard 要求 base64 编码的私钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:498 +#: htdocs/luci-static/resources/view/homeproxy/node.js:928 msgid "Write proxy protocol in the connection header." msgstr "在连接头中写入代理协议。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:889 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1392 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1319 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1832 msgid "Xudp (Xray-core)" msgstr "Xudp (Xray-core)" @@ -2962,17 +3171,17 @@ msgstr "您的 %s 已成功上传。大小:%sB。" msgid "ZeroSSL" msgstr "ZeroSSL" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1520 #: htdocs/luci-static/resources/view/homeproxy/server.js:772 msgid "certificate" msgstr "证书" -#: htdocs/luci-static/resources/view/homeproxy/client.js:716 +#: htdocs/luci-static/resources/view/homeproxy/client.js:739 msgid "connect UDP connections" msgstr "主动连接 UDP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1050 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1058 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1480 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1488 #: htdocs/luci-static/resources/view/homeproxy/server.js:572 #: htdocs/luci-static/resources/view/homeproxy/server.js:580 msgid "default" @@ -2982,44 +3191,52 @@ msgstr "默认" msgid "failed" msgstr "失败" -#: htdocs/luci-static/resources/view/homeproxy/node.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1206 #: htdocs/luci-static/resources/view/homeproxy/server.js:402 msgid "gRPC" msgstr "gRPC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:818 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1248 msgid "gRPC permit without stream" msgstr "gRPC 允许无活动连接" -#: htdocs/luci-static/resources/view/homeproxy/node.js:813 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1243 #: htdocs/luci-static/resources/view/homeproxy/server.js:430 msgid "gRPC service name" msgstr "gRPC 服务名称" -#: htdocs/luci-static/resources/view/homeproxy/client.js:311 +#: htdocs/luci-static/resources/view/homeproxy/client.js:334 msgid "gVisor" msgstr "gVisor" #: htdocs/luci-static/resources/homeproxy.js:306 #: htdocs/luci-static/resources/homeproxy.js:326 -#: htdocs/luci-static/resources/view/homeproxy/client.js:195 -#: htdocs/luci-static/resources/view/homeproxy/client.js:230 -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1357 -#: htdocs/luci-static/resources/view/homeproxy/node.js:488 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 +#: htdocs/luci-static/resources/view/homeproxy/client.js:253 +#: htdocs/luci-static/resources/view/homeproxy/client.js:527 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1380 +#: htdocs/luci-static/resources/view/homeproxy/node.js:918 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1561 #: htdocs/luci-static/resources/view/homeproxy/server.js:235 msgid "non-empty value" msgstr "非空值" -#: htdocs/luci-static/resources/view/homeproxy/node.js:631 -#: htdocs/luci-static/resources/view/homeproxy/node.js:887 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1390 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1919 +msgid "The default rule set directory is /etc/homeproxy/ruleset/." +msgstr "规则集默认目录为 /etc/homeproxy/ruleset/。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1975 +msgid "absolute path" +msgstr "绝对路径" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1061 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1317 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1830 msgid "none" msgstr "无" -#: htdocs/luci-static/resources/view/homeproxy/node.js:888 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1391 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1318 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1831 msgid "packet addr (v2ray-core v5+)" msgstr "packet addr (v2ray-core v5+)" @@ -3031,7 +3248,7 @@ msgstr "通过" msgid "private key" msgstr "私钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:631 +#: htdocs/luci-static/resources/view/homeproxy/client.js:654 msgid "quic-go / uquic chrome" msgstr "quic-go / uquic chrome" @@ -3043,11 +3260,11 @@ msgstr "sing-box 客户端" msgid "sing-box server" msgstr "sing-box 服务端" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1543 msgid "uTLS fingerprint" msgstr "uTLS 指纹" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1114 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1544 msgid "" "uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting " "resistance." @@ -3059,7 +3276,7 @@ msgid "unchecked" msgstr "未检查" #: htdocs/luci-static/resources/homeproxy.js:237 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1302 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1735 msgid "unique UCI identifier" msgstr "独立 UCI 标识" @@ -3067,35 +3284,35 @@ msgstr "独立 UCI 标识" msgid "unique value" msgstr "独立值" -#: htdocs/luci-static/resources/view/homeproxy/node.js:500 -#: htdocs/luci-static/resources/view/homeproxy/node.js:645 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 +#: htdocs/luci-static/resources/view/homeproxy/node.js:930 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1075 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1608 msgid "v1" msgstr "v1" -#: htdocs/luci-static/resources/view/homeproxy/node.js:501 -#: htdocs/luci-static/resources/view/homeproxy/node.js:646 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1179 +#: htdocs/luci-static/resources/view/homeproxy/node.js:931 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1076 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1609 msgid "v2" msgstr "v2" -#: htdocs/luci-static/resources/view/homeproxy/node.js:647 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1077 msgid "v3" msgstr "v3" -#: htdocs/luci-static/resources/view/homeproxy/client.js:207 -#: htdocs/luci-static/resources/view/homeproxy/client.js:211 -#: htdocs/luci-static/resources/view/homeproxy/client.js:241 -#: htdocs/luci-static/resources/view/homeproxy/client.js:245 +#: htdocs/luci-static/resources/view/homeproxy/client.js:231 +#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:268 msgid "valid DNS server address" msgstr "有效 DNS 服务器地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:518 -#: htdocs/luci-static/resources/view/homeproxy/client.js:521 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1362 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1365 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1359 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1385 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1388 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1796 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1799 msgid "valid URL" msgstr "有效网址" @@ -3103,8 +3320,8 @@ msgstr "有效网址" msgid "valid base64 key with %d characters" msgstr "包含 %d 个字符的有效 base64 密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1539 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1530 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1562 msgid "valid hostname" msgstr "有效主机名" @@ -3112,10 +3329,181 @@ msgstr "有效主机名" msgid "valid port range (port1:port2)" msgstr "有效端口范围(port1:port2)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:274 +#: htdocs/luci-static/resources/view/homeproxy/client.js:297 msgid "valid port value" msgstr "有效端口值" #: htdocs/luci-static/resources/homeproxy.js:328 msgid "valid uuid" msgstr "有效 uuid" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Panel Settings" +msgstr "面版设置" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI path" +msgstr "UI 路径" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI download URL" +msgstr "UI 下载地址" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "UI download detour" +msgstr "UI 下载节点" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "API listen" +msgstr "API 监听" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Use 0.0.0.0:port to open the panel from your browser." +msgstr "使用 0.0.0.0:端口 才能从浏览器打开面板。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "API password" +msgstr "API 密码" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Default mode" +msgstr "默认模式" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP Settings" +msgstr "NTP 设置" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable NTP" +msgstr "启用 NTP" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP server address" +msgstr "NTP 服务地址" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP server port" +msgstr "NTP 服务端口" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP time synchronization interval" +msgstr "NTP 时间同步间隔" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "NTP service always uses direct connection and mainly provides accurate time for sing-box features that depend on it, such as TLS certificate verification, VMess, Reality/uTLS and other time-sensitive connection scenarios." +msgstr "NTP服务固定走直连,主要提供给Sing-box某些依赖准确时间的功能使用,如:TLS证书校验、VMess、Reality/uTLS等对时间敏感的连接场景。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Time format examples: 1m = 1 minute, 1h = 1 hour, 1d = 1 day." +msgstr "时间格式示例:1m = 1分钟,1h = 1小时,1d = 1天。" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache Settings" +msgstr "缓存设置" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persistent Cache Settings" +msgstr "持久化缓存设置" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable cache" +msgstr "启用缓存" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Enable cache file" +msgstr "启用缓存文件" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache file path" +msgstr "缓存文件路径" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persistent cache file path" +msgstr "持久化缓存文件路径" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Cache RDRC" +msgstr "缓存 RDRC" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Persist RDRC cache" +msgstr "持久化 RDRC 缓存" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Status" +msgstr "状态" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Plugin Version" +msgstr "插件版本" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Core Version" +msgstr "核心版本" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Core Status" +msgstr "核心状态" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Running" +msgstr "运行中" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Running: %s" +msgstr "运行中:%s" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Not Running" +msgstr "未运行" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Reload Service" +msgstr "重载服务" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Restart Service" +msgstr "重启服务" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Update Panel" +msgstr "更新面版" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Update panel failed." +msgstr "更新面版失败" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Clash API unavailable." +msgstr "Clash API 不可用" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Open Panel" +msgstr "打开面版" + +#: htdocs/luci-static/resources/view/homeproxy/client.js +msgid "Upload Panel ZIP" +msgstr "上传面版ZIP" + +#~ msgid "Real Proxy" +#~ msgstr "Real Proxy" + +#~ msgid "HTTP3" +#~ msgstr "HTTP3" +msgid "Baidu Connection" +msgstr "百度连接" + +msgid "Google Connection" +msgstr "谷歌连接" + +msgid "GitHub Connection" +msgstr "GitHub 连接" + +msgid "YouTube Connection" +msgstr "YouTube 连接" + +msgid "Click to check" +msgstr "点我检测" + +msgid "Checking..." +msgstr "检测中..." diff --git a/luci-app-homeproxy/root/etc/config/homeproxy b/luci-app-homeproxy/root/etc/config/homeproxy index 1a53b56e6..d6e62dc5f 100644 --- a/luci-app-homeproxy/root/etc/config/homeproxy +++ b/luci-app-homeproxy/root/etc/config/homeproxy @@ -25,8 +25,8 @@ config homeproxy 'migration' config homeproxy 'config' option main_node 'nil' option main_udp_node 'same' - option dns_server '8.8.8.8' - option china_dns_server '223.5.5.5' + option dns_server 'https://dns.quad9.net/dns-query' + option china_dns_server 'https://dns.alidns.com/dns-query' option routing_mode 'bypass_mainland_china' option routing_port 'common' option proxy_mode 'redirect_tproxy' @@ -38,20 +38,36 @@ config homeproxy 'control' option lan_proxy_mode 'disabled' list wan_proxy_ipv4_ips '91.105.192.0/23' list wan_proxy_ipv4_ips '91.108.4.0/22' - list wan_proxy_ipv4_ips '91.108.8.0/22' - list wan_proxy_ipv4_ips '91.108.16.0/22' - list wan_proxy_ipv4_ips '91.108.12.0/22' - list wan_proxy_ipv4_ips '91.108.20.0/22' + list wan_proxy_ipv4_ips '91.108.8.0/21' + list wan_proxy_ipv4_ips '91.108.16.0/21' list wan_proxy_ipv4_ips '91.108.56.0/22' + list wan_proxy_ipv4_ips '95.161.64.0/20' list wan_proxy_ipv4_ips '149.154.160.0/20' list wan_proxy_ipv4_ips '185.76.151.0/24' - list wan_proxy_ipv4_ips '203.208.50.66/32' list wan_proxy_ipv6_ips '2001:67c:4e8::/48' - list wan_proxy_ipv6_ips '2001:b28:f23c::/48' - list wan_proxy_ipv6_ips '2001:b28:f23d::/48' + list wan_proxy_ipv6_ips '2001:b28:f23c::/47' list wan_proxy_ipv6_ips '2001:b28:f23f::/48' list wan_proxy_ipv6_ips '2a0a:f280::/32' +config homeproxy 'clash_api' + option external_ui '/etc/homeproxy/run/ui' + option external_ui_download_url 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip' + option external_ui_download_detour 'direct-out' + option external_controller '0.0.0.0:9095' + option secret 'homeproxy' + option default_mode 'rule' + +config homeproxy 'ntp' + option enabled '1' + option server 'ntp.aliyun.com' + option server_port '123' + option interval '30m' + +config homeproxy 'cache' + option enabled '1' + option path '/etc/homeproxy/cache.db' + option store_rdrc '1' + config homeproxy 'routing' option sniff_override '1' option default_outbound 'direct-out' @@ -66,13 +82,12 @@ config homeproxy 'dns' config homeproxy 'subscription' option auto_update '0' option allow_insecure '0' + option latency_test_mode 'icmp' option packet_encoding 'xudp' option update_via_proxy '0' - option filter_nodes 'blacklist' - list filter_keywords '重置|到期|过期|剩余|套餐' - list filter_keywords 'Expiration|Remaining' + option auto_update_time '0 0 * * *' + option filter_nodes 'disabled' config homeproxy 'server' option enabled '0' option log_level 'warn' - diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.txt b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.txt index f9b5b40d7..8b94e33d4 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.txt +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.txt @@ -3,7 +3,6 @@ 1.8.1.0/24 1.8.8.0/24 1.12.0.0/14 -1.18.128.0/24 1.24.0.0/13 1.45.0.0/16 1.48.0.0/14 @@ -24,13 +23,9 @@ 1.192.0.0/13 1.202.0.0/15 1.204.0.0/14 -2.56.255.0/24 -2.58.242.0/24 -2.59.151.0/24 5.10.138.0/23 5.10.140.0/24 5.10.143.0/24 -5.181.219.0/24 8.128.4.0/22 8.128.32.0/19 8.128.64.0/19 @@ -57,8 +52,6 @@ 8.163.0.0/16 8.164.0.0/14 8.168.0.0/15 -8.176.0.0/17 -8.176.128.0/19 14.16.0.0/12 14.103.0.0/16 14.104.0.0/13 @@ -67,18 +60,12 @@ 14.144.0.0/12 14.204.0.0/15 14.208.0.0/12 -14.241.232.0/21 -14.255.16.0/24 -14.255.238.0/24 -14.255.254.0/24 16.2.142.0/23 -23.162.120.0/24 -23.247.128.0/24 -23.247.130.0/24 +23.161.8.0/24 27.0.128.0/24 27.0.130.0/23 27.0.132.0/22 -27.0.164.0/22 +27.0.160.0/21 27.0.204.0/22 27.0.208.0/21 27.8.0.0/13 @@ -98,7 +85,7 @@ 27.184.0.0/13 27.192.0.0/11 27.224.0.0/14 -31.40.214.0/24 +31.77.184.0/22 36.1.0.0/16 36.4.0.0/14 36.16.0.0/12 @@ -107,11 +94,29 @@ 36.40.0.0/13 36.48.0.0/15 36.50.226.0/24 +36.51.224.0/23 +36.51.226.0/24 +36.51.248.0/24 36.51.253.0/24 +36.51.254.0/24 36.56.0.0/13 36.96.0.0/12 36.112.0.0/15 -36.128.0.0/10 +36.114.0.0/16 +36.128.0.0/13 +36.136.0.0/14 +36.140.0.0/17 +36.140.128.0/18 +36.140.192.0/20 +36.140.208.0/21 +36.140.216.0/23 +36.140.218.0/24 +36.140.220.0/22 +36.140.224.0/19 +36.141.0.0/16 +36.142.0.0/15 +36.144.0.0/12 +36.160.0.0/11 36.192.0.0/16 36.206.0.0/16 36.212.0.0/15 @@ -122,22 +127,22 @@ 36.255.128.0/22 36.255.164.0/24 39.64.0.0/11 -39.96.0.0/13 +39.96.0.0/16 +39.97.0.0/17 +39.97.128.0/18 +39.97.192.0/21 +39.97.200.0/22 +39.97.208.0/20 +39.97.224.0/19 +39.98.0.0/15 +39.100.0.0/14 39.104.0.0/14 39.108.0.0/16 39.128.0.0/10 -40.0.176.0/20 -40.0.248.0/21 40.72.0.0/15 40.125.128.0/17 40.126.64.0/18 40.162.0.0/16 -40.198.10.0/24 -40.198.16.0/21 -40.198.24.0/23 -40.251.225.0/24 -40.251.227.0/24 -40.251.228.0/24 42.4.0.0/14 42.48.0.0/15 42.51.0.0/16 @@ -188,7 +193,11 @@ 42.240.16.0/23 42.240.20.0/22 42.240.48.0/24 -42.240.128.0/17 +42.240.128.0/18 +42.240.192.0/19 +42.240.224.0/20 +42.240.240.0/21 +42.240.248.0/23 42.242.0.0/15 42.244.0.0/14 42.248.0.0/15 @@ -213,13 +222,15 @@ 43.224.240.0/24 43.225.76.0/22 43.225.84.0/23 +43.225.87.0/24 43.225.120.0/22 43.225.180.0/22 43.225.208.0/22 +43.225.216.0/22 43.225.255.0/24 43.226.32.0/19 43.226.64.0/20 -43.226.116.0/22 +43.226.112.0/21 43.226.128.0/19 43.226.164.0/22 43.226.236.0/22 @@ -239,25 +250,26 @@ 43.228.48.0/20 43.228.64.0/21 43.228.76.0/22 +43.228.116.0/22 43.228.204.0/22 43.228.240.0/22 43.229.48.0/22 -43.229.184.0/23 +43.229.184.0/22 43.229.216.0/22 43.230.72.0/22 43.230.221.0/24 43.230.222.0/23 +43.231.32.0/22 43.231.41.0/24 43.231.46.0/23 43.231.96.0/20 43.231.144.0/20 43.231.160.0/21 -43.231.168.0/23 -43.231.170.0/24 +43.231.168.0/22 43.239.120.0/22 -43.239.172.0/23 +43.239.172.0/24 43.240.0.0/22 -43.240.60.0/22 +43.240.56.0/21 43.240.72.0/22 43.240.124.0/22 43.240.128.0/22 @@ -268,10 +280,9 @@ 43.240.200.0/23 43.240.202.0/24 43.240.204.0/22 -43.240.220.0/22 +43.240.216.0/21 43.241.16.0/22 -43.241.48.0/24 -43.241.50.0/23 +43.241.48.0/22 43.241.76.0/22 43.241.180.0/22 43.241.208.0/20 @@ -284,6 +295,7 @@ 43.242.164.0/22 43.242.180.0/22 43.242.192.0/21 +43.242.204.0/22 43.242.252.0/22 43.243.4.0/22 43.243.12.0/22 @@ -312,13 +324,13 @@ 43.248.108.0/22 43.248.112.0/21 43.248.128.0/20 +43.248.184.0/21 43.248.192.0/20 43.248.232.0/22 43.248.244.0/22 -43.249.0.0/23 -43.249.2.0/24 43.249.136.0/22 43.249.144.0/22 +43.249.168.0/22 43.249.192.0/22 43.249.236.0/22 43.250.4.0/22 @@ -360,18 +372,19 @@ 43.255.84.0/22 43.255.96.0/22 43.255.184.0/22 +43.255.192.0/22 43.255.200.0/22 43.255.212.0/22 -43.255.224.0/22 -45.12.88.0/24 -45.12.90.0/24 +43.255.224.0/21 +44.30.131.0/24 +44.31.28.0/24 +44.31.212.0/24 +44.31.216.0/24 +44.32.191.0/24 +45.9.11.0/24 45.40.192.0/18 -45.61.200.0/23 -45.61.226.0/24 45.65.20.0/22 -45.67.223.0/24 -45.81.34.0/24 -45.82.239.0/24 +45.65.24.0/22 45.112.232.0/22 45.113.20.0/22 45.113.24.0/22 @@ -391,13 +404,14 @@ 45.117.8.0/22 45.117.68.0/22 45.119.60.0/22 -45.119.68.0/22 +45.119.64.0/21 45.119.104.0/23 45.119.116.0/22 45.120.100.0/22 45.120.164.0/22 45.120.243.0/24 -45.123.117.0/24 +45.121.52.0/24 +45.121.55.0/24 45.123.128.0/22 45.123.168.0/21 45.123.176.0/21 @@ -406,18 +420,19 @@ 45.124.76.0/22 45.124.80.0/22 45.124.124.0/22 -45.125.25.0/24 45.125.44.0/22 45.125.56.0/22 45.126.112.0/22 45.126.120.0/22 45.127.129.0/24 45.127.144.0/21 -45.150.236.0/23 +45.132.80.0/24 45.248.8.0/22 45.248.108.0/23 45.248.110.0/24 +45.248.204.0/22 45.249.212.0/22 +45.250.28.0/22 45.250.32.0/21 45.250.40.0/22 45.250.152.0/23 @@ -427,9 +442,10 @@ 45.251.8.0/22 45.251.20.0/22 45.251.88.0/21 -45.251.100.0/22 +45.251.96.0/21 45.251.120.0/22 45.252.0.0/22 +45.252.48.0/22 45.252.104.0/22 45.253.24.0/22 45.253.32.0/24 @@ -455,10 +471,7 @@ 47.120.0.0/14 49.4.0.0/18 49.4.64.0/19 -49.4.96.0/21 -49.4.104.0/22 -49.4.108.0/23 -49.4.110.0/24 +49.4.96.0/20 49.4.112.0/21 49.4.120.0/22 49.4.124.0/23 @@ -476,7 +489,6 @@ 49.221.128.0/19 49.232.0.0/14 49.239.192.0/18 -50.114.187.0/24 52.80.0.0/15 52.82.0.0/17 52.82.128.0/23 @@ -524,7 +536,7 @@ 58.68.236.0/24 58.68.247.0/24 58.82.0.0/22 -58.83.128.0/17 +58.83.0.0/16 58.87.64.0/18 58.99.128.0/17 58.100.0.0/15 @@ -607,13 +619,31 @@ 59.153.164.0/22 59.153.168.0/24 59.172.0.0/14 +59.191.0.0/17 59.252.0.0/16 60.0.0.0/11 60.63.0.0/16 60.160.0.0/11 60.194.0.0/15 60.200.0.0/24 -60.204.0.0/14 +60.204.0.0/17 +60.204.128.0/18 +60.204.192.0/20 +60.204.208.0/21 +60.204.216.0/22 +60.204.220.0/23 +60.204.222.0/24 +60.204.223.0/26 +60.204.223.64/27 +60.204.223.97/32 +60.204.223.98/31 +60.204.223.100/30 +60.204.223.104/29 +60.204.223.112/28 +60.204.223.128/25 +60.204.224.0/19 +60.205.0.0/16 +60.206.0.0/15 60.208.0.0/12 60.232.0.0/16 60.235.0.0/16 @@ -623,12 +653,6 @@ 60.253.128.0/17 60.255.0.0/16 61.4.80.0/23 -61.10.96.0/20 -61.10.128.0/20 -61.10.160.0/19 -61.15.0.0/18 -61.18.0.0/18 -61.18.64.0/19 61.28.20.0/24 61.28.22.0/24 61.28.49.0/24 @@ -638,19 +662,34 @@ 61.47.128.0/18 61.48.0.0/13 61.87.192.0/18 -61.114.80.0/20 -61.128.0.0/10 +61.128.0.0/11 +61.160.0.0/12 +61.176.0.0/13 +61.184.0.0/15 +61.186.0.0/23 +61.186.3.0/24 +61.186.4.0/22 +61.186.8.0/21 +61.186.16.0/20 +61.186.32.0/19 +61.186.64.0/18 +61.186.128.0/17 +61.187.0.0/16 +61.188.0.0/14 61.232.0.0/14 +61.236.80.0/20 +61.236.96.0/20 +61.236.112.0/21 61.237.0.0/16 61.240.0.0/14 62.234.0.0/16 63.140.0.0/24 63.140.3.0/24 -64.188.38.0/23 -64.188.40.0/22 -64.188.44.0/24 -66.102.240.0/21 -66.102.248.0/22 +66.102.240.0/24 +66.102.242.0/23 +66.102.244.0/22 +66.102.248.0/23 +66.102.251.0/24 66.102.252.0/24 66.102.254.0/23 68.79.0.0/18 @@ -665,21 +704,24 @@ 71.132.0.0/18 71.136.64.0/18 71.137.0.0/18 -72.163.240.0/23 -72.163.248.0/22 -74.114.51.0/24 -74.214.161.0/24 -77.73.8.0/24 -81.23.208.0/20 +78.105.147.0/24 +78.105.182.0/23 81.68.0.0/14 81.173.18.0/23 81.173.20.0/22 81.173.28.0/24 +82.22.31.0/24 +82.108.198.0/23 +82.109.96.0/23 +82.110.234.0/23 82.156.0.0/15 -87.232.115.0/24 -87.254.207.0/24 -93.183.14.0/24 -93.183.18.0/24 +86.38.235.0/24 +87.76.221.0/24 +87.76.222.0/24 +87.83.1.0/24 +87.86.189.0/24 +88.209.226.0/24 +89.213.2.0/24 94.191.0.0/17 101.1.0.0/22 101.2.172.0/22 @@ -701,11 +743,11 @@ 101.50.56.0/22 101.52.4.0/24 101.52.6.0/24 -101.52.68.0/23 +101.52.68.0/22 101.52.112.0/21 101.52.124.0/22 101.52.128.0/20 -101.52.204.0/22 +101.52.200.0/21 101.52.212.0/22 101.52.216.0/21 101.52.232.0/23 @@ -728,10 +770,7 @@ 101.104.144.0/20 101.104.160.0/20 101.106.0.0/19 -101.124.0.0/20 -101.124.19.0/24 -101.124.22.0/24 -101.124.62.0/24 +101.124.0.0/16 101.125.0.0/22 101.125.4.0/23 101.125.6.0/24 @@ -751,9 +790,12 @@ 101.128.0.0/22 101.129.0.0/16 101.132.0.0/15 +101.144.0.0/12 +101.197.0.0/16 101.198.0.0/22 101.198.4.0/24 -101.198.196.0/22 +101.198.160.0/19 +101.198.192.0/19 101.199.48.0/20 101.199.112.0/24 101.199.128.0/23 @@ -764,9 +806,7 @@ 101.204.0.0/14 101.224.0.0/13 101.234.76.0/22 -101.236.0.0/18 -101.236.64.0/20 -101.236.128.0/17 +101.236.0.0/16 101.237.0.0/19 101.237.33.0/24 101.237.34.0/23 @@ -823,13 +863,15 @@ 103.14.132.0/22 103.14.136.0/22 103.15.4.0/22 +103.15.96.0/22 103.16.124.0/22 103.17.40.0/22 103.17.228.0/22 103.19.46.0/23 103.19.64.0/22 103.19.232.0/22 -103.20.32.0/22 +103.20.32.0/24 +103.20.34.0/23 103.20.112.0/22 103.20.128.0/22 103.20.160.0/22 @@ -844,7 +886,6 @@ 103.24.116.0/22 103.24.176.0/22 103.24.228.0/22 -103.24.248.0/22 103.25.20.0/22 103.25.24.0/22 103.25.36.0/22 @@ -857,35 +898,39 @@ 103.27.24.0/22 103.27.240.0/22 103.28.8.0/24 -103.28.147.0/24 103.28.204.0/22 103.28.212.0/22 103.29.16.0/22 103.29.24.0/23 103.29.136.0/22 -103.30.51.0/24 103.30.148.0/24 103.30.151.0/24 103.31.48.0/22 103.31.200.0/22 103.35.104.0/22 -103.35.254.0/24 +103.36.28.0/22 103.36.36.0/22 -103.36.63.0/24 +103.36.60.0/22 103.36.96.0/22 103.36.132.0/22 103.36.136.0/22 -103.36.165.0/24 -103.36.168.0/24 -103.36.172.0/22 +103.36.164.0/24 +103.36.165.0/29 +103.36.165.8/32 +103.36.165.10/31 +103.36.165.12/30 +103.36.165.16/28 +103.36.165.32/27 +103.36.165.64/26 +103.36.165.128/25 +103.36.166.0/23 +103.36.168.0/21 103.36.192.0/20 103.36.208.0/22 103.36.220.0/22 -103.37.4.0/24 103.37.12.0/22 -103.37.18.0/23 -103.37.44.0/23 -103.37.46.0/24 +103.37.16.0/22 +103.37.44.0/22 103.37.72.0/22 103.37.100.0/22 103.37.136.0/21 @@ -893,7 +938,8 @@ 103.37.160.0/21 103.37.172.0/22 103.38.40.0/22 -103.38.76.0/24 +103.38.76.0/22 +103.38.84.0/22 103.38.92.0/23 103.38.116.0/22 103.38.224.0/22 @@ -914,20 +960,24 @@ 103.41.116.0/22 103.41.164.0/22 103.41.232.0/23 +103.42.8.0/22 103.42.76.0/22 +103.43.132.0/24 103.43.134.0/23 -103.43.184.0/21 +103.43.184.0/22 103.43.240.0/23 -103.44.58.0/23 +103.44.56.0/22 103.44.80.0/22 103.44.144.0/22 103.44.168.0/22 +103.44.176.0/22 103.44.236.0/22 103.44.240.0/20 103.45.72.0/21 103.45.128.0/19 103.45.160.0/22 103.45.168.0/23 +103.45.171.0/24 103.45.172.0/22 103.45.176.0/20 103.45.248.0/22 @@ -940,10 +990,11 @@ 103.46.168.0/22 103.47.48.0/22 103.47.80.0/22 +103.48.232.0/23 +103.48.235.0/24 103.49.12.0/22 103.49.108.0/22 103.49.176.0/24 -103.50.91.0/24 103.52.100.0/22 103.52.172.0/22 103.52.176.0/22 @@ -951,6 +1002,7 @@ 103.53.124.0/22 103.53.208.0/24 103.53.211.0/24 +103.54.48.0/22 103.55.172.0/22 103.55.228.0/22 103.56.60.0/22 @@ -958,16 +1010,15 @@ 103.56.100.0/22 103.56.104.0/22 103.56.152.0/22 +103.56.184.0/22 103.57.12.0/22 103.57.136.0/23 103.57.139.0/24 -103.57.203.0/24 -103.59.112.0/22 +103.59.112.0/21 103.59.124.0/22 103.59.148.0/23 103.59.151.0/24 103.59.168.0/23 -103.60.32.0/22 103.60.164.0/22 103.60.228.0/23 103.60.236.0/22 @@ -1017,16 +1068,15 @@ 103.78.228.0/22 103.79.24.0/22 103.79.200.0/22 -103.79.228.0/23 103.81.4.0/22 103.81.48.0/22 -103.81.72.0/22 103.81.123.0/24 103.81.200.0/22 103.82.224.0/22 103.83.44.0/22 103.83.64.0/22 103.83.72.0/22 +103.84.170.0/23 103.85.84.0/22 103.85.147.0/24 103.85.164.0/22 @@ -1054,12 +1104,12 @@ 103.95.68.0/22 103.95.252.0/22 103.96.8.0/22 -103.96.148.0/22 103.96.214.0/23 103.96.224.0/23 103.97.60.0/24 103.97.112.0/23 103.97.144.0/22 +103.97.175.0/24 103.98.44.0/22 103.98.92.0/22 103.98.96.0/22 @@ -1067,18 +1117,19 @@ 103.98.127.0/24 103.98.220.0/22 103.98.248.0/23 +103.98.251.0/24 103.98.252.0/22 103.99.104.0/22 -103.99.178.0/24 103.100.64.0/22 103.101.124.0/23 103.101.180.0/22 -103.102.196.0/24 +103.102.196.0/22 103.102.200.0/22 103.102.213.0/24 103.102.214.0/24 103.103.12.0/24 103.103.36.0/24 +103.104.155.0/24 103.104.252.0/22 103.105.0.0/22 103.105.12.0/22 @@ -1091,6 +1142,7 @@ 103.106.36.0/22 103.106.252.0/22 103.107.0.0/22 +103.107.188.0/22 103.107.216.0/21 103.108.244.0/24 103.108.247.0/24 @@ -1116,9 +1168,10 @@ 103.117.220.0/22 103.118.52.0/22 103.118.60.0/22 -103.118.173.0/24 103.120.72.0/22 103.120.88.0/22 +103.120.196.0/22 +103.120.224.0/22 103.121.52.0/22 103.121.164.0/23 103.121.166.0/24 @@ -1128,8 +1181,10 @@ 103.125.236.0/22 103.126.1.0/24 103.126.124.0/22 +103.130.160.0/23 103.131.138.0/23 103.131.152.0/22 +103.131.168.0/23 103.132.22.0/23 103.132.212.0/23 103.133.128.0/23 @@ -1138,16 +1193,17 @@ 103.135.192.0/23 103.135.195.0/24 103.135.196.0/22 +103.135.236.0/24 103.137.60.0/24 103.139.172.0/23 103.139.212.0/23 103.140.14.0/23 103.141.10.0/23 +103.142.82.0/24 103.142.96.0/23 103.142.234.0/23 103.143.16.0/22 -103.143.92.0/23 -103.143.230.0/24 +103.143.228.0/23 103.144.66.0/23 103.144.70.0/24 103.144.158.0/23 @@ -1156,14 +1212,13 @@ 103.147.124.0/24 103.149.242.0/24 103.149.244.0/22 +103.150.10.0/23 103.150.24.0/23 -103.150.36.0/23 103.150.164.0/23 103.151.148.0/23 103.152.28.0/23 103.152.56.0/23 103.152.76.0/23 -103.152.150.0/23 103.152.186.0/23 103.152.209.0/24 103.152.224.0/24 @@ -1188,6 +1243,7 @@ 103.170.4.0/23 103.170.212.0/23 103.174.94.0/23 +103.175.197.0/24 103.177.28.0/23 103.179.78.0/23 103.181.234.0/24 @@ -1218,6 +1274,9 @@ 103.197.228.0/22 103.198.60.0/22 103.198.124.0/22 +103.198.216.0/21 +103.198.224.0/20 +103.198.244.0/22 103.200.136.0/21 103.200.144.0/21 103.200.220.0/22 @@ -1256,10 +1315,7 @@ 103.212.12.0/22 103.212.48.0/23 103.212.109.0/24 -103.213.49.0/24 -103.213.50.0/23 -103.213.52.0/22 -103.213.56.0/21 +103.213.48.0/20 103.213.64.0/19 103.213.96.0/22 103.213.132.0/22 @@ -1270,24 +1326,27 @@ 103.215.36.0/22 103.215.44.0/24 103.215.140.0/22 +103.216.4.0/22 103.216.136.0/22 103.216.152.0/22 103.216.252.0/24 103.217.184.0/21 103.217.192.0/20 103.218.216.0/22 -103.219.28.0/22 +103.219.24.0/21 103.219.32.0/21 103.219.92.0/24 103.219.100.0/23 103.219.176.0/22 103.219.184.0/22 103.220.52.0/22 -103.220.56.0/22 +103.220.56.0/21 103.220.64.0/22 103.220.92.0/22 103.220.124.0/22 -103.220.128.0/19 +103.220.128.0/20 +103.220.144.0/21 +103.220.152.0/22 103.220.160.0/21 103.220.240.0/22 103.221.140.0/22 @@ -1298,6 +1357,7 @@ 103.222.216.0/22 103.223.132.0/22 103.223.144.0/20 +103.224.220.0/22 103.224.232.0/22 103.226.57.0/24 103.227.76.0/22 @@ -1326,26 +1386,29 @@ 103.233.4.0/22 103.233.52.0/22 103.233.128.0/22 +103.233.136.0/24 103.233.138.0/24 103.233.162.0/23 103.234.20.0/22 103.234.56.0/22 103.234.128.0/23 103.234.131.0/24 -103.235.102.0/23 +103.235.85.0/24 +103.235.100.0/22 103.235.136.0/22 103.235.144.0/24 103.235.220.0/22 -103.235.224.0/20 -103.235.244.0/22 -103.235.248.0/21 +103.235.224.0/19 103.236.52.0/22 103.236.56.0/21 103.236.64.0/19 -103.236.96.0/22 +103.236.96.0/23 +103.236.98.0/24 103.236.120.0/22 103.236.244.0/22 -103.236.248.0/21 +103.236.248.0/23 +103.236.250.0/24 +103.236.252.0/22 103.237.8.0/22 103.237.28.0/23 103.237.176.0/20 @@ -1354,7 +1417,7 @@ 103.237.240.0/21 103.237.248.0/23 103.238.0.0/21 -103.238.16.0/22 +103.238.18.0/23 103.238.48.0/24 103.238.52.0/22 103.238.96.0/23 @@ -1363,14 +1426,15 @@ 103.238.140.0/22 103.238.144.0/22 103.238.160.0/22 -103.238.184.0/24 -103.238.186.0/23 -103.238.188.0/22 +103.238.180.0/22 +103.238.184.0/21 103.238.204.0/22 103.239.68.0/22 103.239.152.0/22 -103.239.184.0/22 +103.239.184.0/23 +103.239.186.0/24 103.239.192.0/22 +103.239.204.0/22 103.239.224.0/22 103.239.244.0/22 103.240.16.0/22 @@ -1379,6 +1443,8 @@ 103.240.124.0/22 103.240.244.0/22 103.241.95.0/24 +103.242.128.0/24 +103.242.130.0/24 103.242.168.0/23 103.242.172.0/22 103.242.212.0/22 @@ -1388,6 +1454,7 @@ 103.244.64.0/22 103.244.80.0/22 103.244.232.0/22 +103.245.23.0/24 103.245.128.0/22 103.246.152.0/22 103.247.168.0/22 @@ -1395,12 +1462,12 @@ 103.247.191.0/24 103.248.152.0/22 103.248.224.0/22 +103.249.52.0/22 103.249.136.0/24 103.249.244.0/22 103.249.252.0/22 103.250.32.0/22 103.250.104.0/22 -103.250.124.0/22 103.250.192.0/22 103.250.216.0/22 103.250.248.0/21 @@ -1409,14 +1476,15 @@ 103.251.96.0/22 103.251.124.0/22 103.251.160.0/22 -103.251.204.0/24 +103.251.204.0/23 103.251.207.0/24 103.251.240.0/22 -103.252.36.0/24 +103.252.36.0/22 103.252.172.0/22 103.252.248.0/22 103.253.60.0/22 103.253.204.0/22 +103.253.232.0/22 103.254.68.0/22 103.254.76.0/22 103.254.112.0/22 @@ -1425,6 +1493,9 @@ 103.255.92.0/22 103.255.140.0/22 103.255.200.0/22 +104.192.108.0/23 +104.192.110.0/24 +104.234.102.0/24 106.0.4.0/22 106.2.37.0/24 106.2.40.0/23 @@ -1446,16 +1517,16 @@ 106.2.104.0/21 106.2.112.0/21 106.2.124.0/22 -106.2.128.0/19 +106.2.128.0/20 +106.2.144.0/21 +106.2.152.0/22 +106.2.156.0/23 106.3.0.0/19 106.3.32.0/20 106.3.80.0/22 106.3.88.0/21 -106.3.128.0/20 -106.3.144.0/22 -106.3.148.0/23 -106.3.151.0/24 -106.3.152.0/21 +106.3.96.0/22 +106.3.128.0/19 106.3.192.0/20 106.3.208.0/21 106.3.224.0/21 @@ -1474,7 +1545,6 @@ 106.14.0.0/15 106.16.0.0/14 106.32.0.0/12 -106.48.16.0/21 106.52.0.0/14 106.56.0.0/14 106.60.0.0/15 @@ -1493,9 +1563,6 @@ 106.224.0.0/14 106.228.0.0/15 106.230.0.0/16 -107.151.212.0/24 -107.190.229.0/24 -108.186.72.0/24 109.244.0.0/16 110.6.0.0/15 110.16.0.0/14 @@ -1504,18 +1571,25 @@ 110.40.14.0/23 110.40.16.0/24 110.40.128.0/17 -110.41.0.0/17 -110.41.128.0/18 -110.41.192.0/20 -110.41.208.0/23 -110.41.211.0/24 -110.41.212.0/22 -110.41.216.0/21 -110.41.224.0/19 -110.42.0.0/15 +110.41.0.0/16 +110.42.0.0/23 +110.42.3.0/24 +110.42.4.0/23 +110.42.6.0/24 +110.42.8.0/22 +110.42.12.0/24 +110.42.14.0/23 +110.42.17.0/24 +110.42.18.0/23 +110.42.20.0/22 +110.42.24.0/21 +110.42.32.0/19 +110.42.64.0/18 +110.42.128.0/17 +110.43.0.0/16 110.51.0.0/16 110.52.0.0/15 -110.56.0.0/16 +110.56.0.0/13 110.64.0.0/15 110.72.0.0/15 110.75.0.0/16 @@ -1524,19 +1598,20 @@ 110.76.184.0/22 110.76.192.0/18 110.77.0.0/17 -110.79.78.0/24 110.80.0.0/13 110.88.0.0/14 110.94.0.0/17 110.96.0.0/16 110.100.0.0/16 -110.113.0.0/16 +110.112.0.0/15 110.114.0.0/16 +110.115.128.0/17 110.116.0.0/16 110.120.0.0/16 110.152.0.0/14 110.156.0.0/15 110.166.0.0/15 +110.172.200.0/21 110.173.8.0/21 110.173.16.0/20 110.173.32.0/20 @@ -1576,7 +1651,7 @@ 111.221.28.0/24 111.221.128.0/17 111.222.0.0/16 -111.223.8.0/21 +111.223.12.0/22 111.224.0.0/14 111.228.0.0/18 111.229.0.0/16 @@ -1592,6 +1667,19 @@ 111.235.182.0/24 112.0.0.0/10 112.64.0.0/14 +112.73.97.0/24 +112.73.98.0/23 +112.73.100.0/22 +112.73.106.0/23 +112.73.108.0/24 +112.73.110.0/24 +112.73.112.0/23 +112.73.115.0/24 +112.73.116.0/24 +112.73.120.0/24 +112.73.122.0/24 +112.73.124.0/23 +112.73.127.0/24 112.74.0.0/16 112.80.0.0/12 112.96.0.0/13 @@ -1614,6 +1702,7 @@ 113.31.96.0/19 113.31.144.0/20 113.31.160.0/19 +113.31.192.0/18 113.44.0.0/16 113.45.0.0/18 113.45.64.0/19 @@ -1655,7 +1744,6 @@ 113.130.112.0/21 113.132.0.0/14 113.136.0.0/13 -113.164.25.0/24 113.194.0.0/15 113.197.101.0/24 113.197.103.0/24 @@ -1675,11 +1763,14 @@ 114.28.68.0/22 114.28.72.0/21 114.28.128.0/18 -114.28.248.0/21 +114.28.248.0/22 +114.28.252.0/24 +114.28.254.0/23 114.55.0.0/16 114.60.0.0/14 114.64.0.0/16 114.66.0.0/18 +114.66.64.0/24 114.66.72.0/21 114.66.81.0/24 114.66.236.0/22 @@ -1691,14 +1782,7 @@ 114.67.60.0/23 114.67.62.0/24 114.67.64.0/18 -114.67.131.0/24 -114.67.136.0/24 -114.67.150.0/24 -114.67.152.0/22 -114.67.156.0/24 -114.67.159.0/24 -114.67.160.0/19 -114.67.192.0/18 +114.67.128.0/17 114.80.0.0/12 114.96.0.0/13 114.104.0.0/14 @@ -1710,17 +1794,7 @@ 114.112.4.0/23 114.112.24.0/21 114.112.32.0/19 -114.112.64.0/24 -114.112.66.0/24 -114.112.70.0/24 -114.112.72.0/21 -114.112.81.0/24 -114.112.82.0/24 -114.112.85.0/24 -114.112.86.0/24 -114.112.88.0/24 -114.112.91.0/24 -114.112.94.0/23 +114.112.64.0/19 114.112.96.0/21 114.112.104.0/22 114.112.136.0/21 @@ -1728,8 +1802,7 @@ 114.112.200.0/21 114.112.208.0/20 114.113.63.0/24 -114.113.65.0/24 -114.113.66.0/24 +114.113.64.0/22 114.113.80.0/22 114.113.84.0/24 114.113.88.0/21 @@ -1745,13 +1818,12 @@ 114.114.112.0/21 114.115.0.0/16 114.116.0.0/15 -114.118.0.0/18 -114.118.64.0/19 -114.118.96.0/21 +114.118.0.0/17 114.119.10.0/24 114.119.32.0/24 114.119.117.0/24 114.119.119.0/24 +114.119.204.0/22 114.132.0.0/16 114.135.0.0/16 114.138.0.0/15 @@ -1831,16 +1903,8 @@ 116.70.64.0/18 116.76.0.0/15 116.78.0.0/16 -116.85.1.0/24 -116.85.2.0/23 -116.85.13.0/24 -116.85.14.0/24 -116.85.16.0/23 -116.85.18.0/24 -116.85.64.0/20 -116.85.240.0/20 +116.85.0.0/16 116.90.80.0/20 -116.90.192.0/20 116.95.0.0/16 116.112.0.0/14 116.116.0.0/15 @@ -1890,6 +1954,7 @@ 116.198.0.0/18 116.198.64.0/21 116.198.72.0/22 +116.198.80.0/21 116.198.96.0/19 116.198.144.0/20 116.198.160.0/20 @@ -1922,20 +1987,14 @@ 117.32.0.0/13 117.40.0.0/14 117.44.0.0/15 +117.48.0.0/18 +117.48.64.0/19 117.48.112.0/20 117.48.128.0/19 117.48.160.0/20 -117.48.192.0/24 -117.48.195.0/24 -117.48.196.0/22 -117.48.200.0/21 -117.48.216.0/23 -117.48.218.0/24 -117.48.220.0/22 -117.48.224.0/21 -117.48.232.0/23 -117.48.234.0/24 -117.48.236.0/22 +117.48.192.0/20 +117.48.216.0/21 +117.48.224.0/20 117.50.0.0/16 117.51.128.0/19 117.51.160.0/23 @@ -1954,6 +2013,7 @@ 117.72.160.0/19 117.72.192.0/19 117.72.240.0/21 +117.72.248.0/22 117.72.255.0/24 117.73.0.0/20 117.73.16.0/21 @@ -1965,6 +2025,7 @@ 117.79.80.0/20 117.79.128.0/21 117.79.144.0/20 +117.79.160.0/21 117.79.224.0/20 117.79.241.0/24 117.79.242.0/24 @@ -1981,22 +2042,39 @@ 117.124.244.0/24 117.124.246.0/24 117.126.0.0/16 -117.128.0.0/10 +117.128.0.0/14 +117.132.0.0/15 +117.134.0.0/17 +117.134.128.0/18 +117.134.203.0/24 +117.134.204.0/22 +117.134.208.0/20 +117.134.224.0/19 +117.135.0.0/16 +117.136.0.0/13 +117.144.0.0/12 +117.160.0.0/11 118.24.0.0/15 +118.26.32.0/22 118.26.48.0/22 118.26.64.0/23 118.26.68.0/23 118.26.72.0/22 118.26.96.0/21 118.26.116.0/22 +118.26.120.0/24 +118.26.126.0/23 118.26.128.0/22 118.26.136.0/22 118.26.160.0/20 118.26.192.0/24 118.26.194.0/23 +118.26.197.0/24 +118.26.198.0/23 +118.26.200.0/21 118.26.208.0/20 -118.26.240.0/20 -118.31.0.0/16 +118.26.224.0/19 +118.30.0.0/15 118.64.0.0/21 118.64.248.0/21 118.65.0.0/21 @@ -2043,11 +2121,12 @@ 118.186.80.0/20 118.186.96.0/20 118.186.112.0/21 -118.186.160.0/19 +118.186.128.0/18 118.186.208.0/21 118.186.240.0/21 118.187.0.0/18 118.187.64.0/19 +118.187.254.0/23 118.188.18.0/23 118.188.20.0/22 118.188.24.0/23 @@ -2055,6 +2134,16 @@ 118.191.0.0/21 118.191.8.0/22 118.191.12.0/24 +118.191.128.0/22 +118.191.152.0/24 +118.191.192.0/23 +118.191.208.0/24 +118.191.216.0/24 +118.191.223.0/24 +118.191.224.0/24 +118.191.240.0/23 +118.191.248.0/23 +118.191.250.0/24 118.192.20.0/24 118.192.64.0/23 118.192.67.0/24 @@ -2063,8 +2152,9 @@ 118.193.96.0/19 118.194.32.0/19 118.194.134.0/23 +118.194.164.0/22 118.194.240.0/21 -118.195.128.0/17 +118.195.0.0/16 118.196.0.0/16 118.197.0.0/18 118.197.64.0/19 @@ -2184,6 +2274,7 @@ 119.80.248.0/21 119.84.0.0/14 119.88.128.0/17 +119.90.32.0/19 119.91.0.0/16 119.96.0.0/13 119.108.0.0/15 @@ -2193,6 +2284,7 @@ 119.148.160.0/20 119.161.120.0/22 119.161.124.0/24 +119.161.183.0/24 119.162.0.0/15 119.164.0.0/14 119.176.0.0/12 @@ -2211,13 +2303,17 @@ 119.253.0.0/20 119.253.32.0/19 119.253.64.0/19 +119.253.249.0/24 +119.253.252.0/24 119.254.0.0/16 +119.255.63.0/24 119.255.128.0/17 120.0.0.0/12 120.24.0.0/14 120.31.66.0/23 120.31.68.0/22 120.31.128.0/20 +120.31.144.0/24 120.32.0.0/12 120.48.0.0/16 120.49.0.0/17 @@ -2257,8 +2353,7 @@ 120.132.64.0/19 120.132.96.0/20 120.132.112.0/24 -120.132.117.0/24 -120.132.118.0/23 +120.132.116.0/22 120.132.120.0/21 120.132.128.0/17 120.133.0.0/16 @@ -2281,8 +2376,7 @@ 121.36.0.0/15 121.39.255.0/24 121.40.0.0/14 -121.46.0.0/20 -121.46.20.0/22 +121.46.0.0/19 121.46.128.0/21 121.46.141.0/24 121.46.192.0/21 @@ -2292,6 +2386,7 @@ 121.46.248.0/22 121.46.252.0/23 121.46.254.0/24 +121.47.0.0/16 121.48.0.0/15 121.51.0.0/16 121.52.160.0/19 @@ -2303,6 +2398,8 @@ 121.58.156.0/22 121.59.4.0/22 121.59.8.0/21 +121.59.160.0/20 +121.59.255.0/24 121.60.0.0/14 121.69.0.0/16 121.70.0.0/17 @@ -2333,9 +2430,13 @@ 122.49.16.0/20 122.49.32.0/20 122.51.0.0/16 +122.64.0.0/16 122.70.0.0/15 +122.72.3.0/24 122.72.16.0/24 122.72.38.0/24 +122.72.57.0/24 +122.72.112.0/24 122.94.0.0/16 122.96.0.0/15 122.102.64.0/20 @@ -2422,8 +2523,10 @@ 123.49.192.0/23 123.49.194.0/24 123.49.231.0/24 +123.49.232.0/24 123.49.240.0/24 123.49.242.0/23 +123.49.245.0/24 123.52.0.0/14 123.56.0.0/15 123.58.0.0/19 @@ -2449,10 +2552,11 @@ 123.58.188.0/22 123.58.224.0/19 123.59.0.0/16 -123.60.0.0/16 +123.60.0.0/15 123.64.0.0/15 123.66.0.0/16 123.77.0.0/16 +123.83.128.0/17 123.88.0.0/15 123.90.0.0/16 123.96.0.0/15 @@ -2466,7 +2570,7 @@ 123.103.40.0/21 123.103.48.0/20 123.103.64.0/18 -123.108.88.0/24 +123.108.88.0/23 123.108.208.0/22 123.108.212.0/23 123.108.220.0/22 @@ -2485,12 +2589,14 @@ 123.242.192.0/21 123.244.0.0/14 123.249.0.0/17 +123.253.226.0/24 123.254.96.0/21 +124.14.1.0/24 +124.14.2.0/23 124.14.5.0/24 -124.14.8.0/24 -124.14.12.0/24 -124.14.15.0/24 -124.14.22.0/24 +124.14.7.0/24 +124.14.8.0/21 +124.14.16.0/21 124.14.64.0/18 124.14.224.0/19 124.16.0.0/15 @@ -2518,7 +2624,10 @@ 124.151.0.0/16 124.152.0.0/16 124.160.0.0/13 -124.172.0.0/14 +124.172.128.0/17 +124.173.64.0/18 +124.173.128.0/18 +124.174.0.0/15 124.192.0.0/15 124.196.12.0/23 124.196.25.0/24 @@ -2553,7 +2662,19 @@ 125.32.0.0/12 125.58.128.0/17 125.62.0.0/18 -125.64.0.0/11 +125.64.0.0/12 +125.80.0.0/13 +125.88.0.0/14 +125.92.0.0/16 +125.93.0.0/20 +125.93.16.0/21 +125.93.24.0/23 +125.93.26.0/24 +125.93.28.0/22 +125.93.32.0/19 +125.93.64.0/18 +125.93.128.0/17 +125.94.0.0/15 125.98.122.0/23 125.98.128.0/17 125.104.0.0/13 @@ -2573,28 +2694,22 @@ 125.215.54.0/24 125.216.0.0/13 125.254.128.0/19 -125.254.168.0/21 +125.254.160.0/20 125.254.176.0/21 -128.107.24.0/23 +125.254.184.0/22 +125.254.188.0/23 +125.254.190.0/24 128.108.0.0/16 129.28.0.0/16 129.204.0.0/16 129.211.0.0/16 -130.36.146.0/23 -131.228.96.0/23 132.232.0.0/16 132.237.134.0/24 -133.226.32.0/20 134.175.0.0/16 +136.0.59.0/24 138.252.72.0/23 139.5.60.0/22 -139.9.0.0/18 -139.9.64.0/19 -139.9.96.0/23 -139.9.98.0/24 -139.9.100.0/22 -139.9.104.0/21 -139.9.112.0/20 +139.9.0.0/17 139.9.128.0/18 139.9.192.0/19 139.9.224.0/20 @@ -2604,7 +2719,8 @@ 139.148.0.0/16 139.155.0.0/16 139.159.0.0/19 -139.159.32.0/22 +139.159.32.0/21 +139.159.40.0/22 139.159.96.0/20 139.159.112.0/22 139.159.132.0/22 @@ -2614,15 +2730,28 @@ 139.159.192.0/18 139.170.0.0/16 139.186.0.0/16 -139.189.0.0/16 +139.189.0.0/18 +139.189.64.0/19 +139.189.96.0/20 +139.189.112.0/21 +139.189.120.0/22 +139.189.124.0/23 +139.189.126.0/24 +139.189.128.0/17 139.196.0.0/16 -139.198.0.0/15 +139.198.0.0/18 +139.198.66.0/23 +139.198.68.0/22 +139.198.72.0/21 +139.198.80.0/20 +139.198.96.0/19 +139.198.128.0/17 +139.199.0.0/16 139.200.0.0/13 139.208.0.0/13 139.217.0.0/16 139.219.0.0/16 -139.220.192.0/22 -139.220.240.0/22 +139.220.128.0/17 139.224.0.0/16 139.226.0.0/15 140.75.0.0/16 @@ -2635,22 +2764,7 @@ 140.210.8.0/21 140.210.16.0/20 140.210.32.0/22 -140.210.65.0/24 -140.210.67.0/24 -140.210.69.0/24 -140.210.70.0/24 -140.210.72.0/23 -140.210.75.0/24 -140.210.77.0/24 -140.210.78.0/23 -140.210.80.0/24 -140.210.83.0/24 -140.210.84.0/24 -140.210.87.0/24 -140.210.88.0/22 -140.210.92.0/24 -140.210.94.0/24 -140.210.96.0/19 +140.210.64.0/18 140.210.128.0/19 140.210.192.0/19 140.210.224.0/20 @@ -2662,18 +2776,21 @@ 140.249.0.0/16 140.250.0.0/16 140.255.0.0/16 +141.11.219.0/24 +143.20.246.0/24 143.64.0.0/16 144.0.0.0/16 144.7.0.0/17 144.12.0.0/16 -144.36.146.0/23 +144.36.147.0/24 144.48.64.0/22 144.48.212.0/22 144.52.0.0/16 +144.79.10.0/23 144.79.54.0/23 +144.79.70.0/23 144.123.0.0/16 -144.225.109.0/24 -144.225.151.0/24 +144.225.186.0/24 144.255.0.0/16 145.14.71.0/24 145.14.72.0/22 @@ -2690,7 +2807,8 @@ 146.222.79.0/24 146.222.81.0/24 146.222.94.0/24 -147.78.132.0/22 +147.78.132.0/24 +147.78.134.0/24 148.70.0.0/16 150.129.136.0/22 150.129.192.0/22 @@ -2702,6 +2820,7 @@ 150.242.76.0/22 150.242.80.0/22 150.242.96.0/22 +150.242.120.0/22 150.242.156.0/23 150.242.168.0/22 150.242.184.0/22 @@ -2710,7 +2829,8 @@ 150.242.244.0/22 150.242.248.0/22 150.255.0.0/16 -151.243.189.0/24 +151.246.165.0/24 +151.247.148.0/23 152.104.128.0/17 152.136.0.0/16 153.0.0.0/16 @@ -2720,22 +2840,17 @@ 153.99.0.0/16 153.101.0.0/16 153.118.0.0/15 -154.8.48.0/20 154.8.128.0/17 154.72.42.0/24 154.72.44.0/24 154.72.47.0/24 -154.89.32.0/20 -154.197.128.0/17 +154.91.158.0/23 155.126.176.0/23 156.107.160.0/23 156.107.170.0/24 156.107.179.0/24 156.107.181.0/24 -156.224.128.0/17 -156.227.239.0/24 -156.232.9.0/24 -156.232.10.0/23 +156.237.104.0/23 156.242.5.0/24 157.0.0.0/16 157.10.35.0/24 @@ -2753,10 +2868,7 @@ 157.148.0.0/16 157.156.0.0/16 157.255.0.0/16 -158.26.192.0/24 -158.26.194.0/24 -158.140.252.0/23 -158.140.255.0/24 +158.140.252.0/22 159.27.0.0/16 159.75.0.0/16 159.226.0.0/16 @@ -2765,12 +2877,11 @@ 160.19.210.0/24 160.20.18.0/23 160.22.188.0/24 +160.22.244.0/23 160.25.194.0/23 160.30.230.0/23 160.83.110.0/24 -160.187.128.0/23 160.191.0.0/24 -160.191.195.0/24 160.202.152.0/22 160.202.212.0/22 160.202.224.0/19 @@ -2780,11 +2891,18 @@ 161.163.28.0/23 161.189.0.0/16 161.207.0.0/16 -162.14.0.0/16 +162.14.0.0/19 +162.14.32.0/21 +162.14.42.0/23 +162.14.44.0/22 +162.14.52.0/22 +162.14.56.0/22 +162.14.64.0/18 +162.14.128.0/17 162.105.0.0/16 163.0.0.0/16 163.47.4.0/22 -163.53.44.0/23 +163.53.44.0/22 163.53.60.0/22 163.53.88.0/21 163.53.128.0/22 @@ -2797,6 +2915,7 @@ 163.177.0.0/16 163.179.0.0/16 163.204.0.0/16 +163.223.173.0/24 163.227.40.0/23 163.228.0.0/16 163.244.246.0/24 @@ -2804,12 +2923,14 @@ 165.101.208.0/23 166.111.0.0/16 167.139.0.0/16 +167.189.0.0/16 167.220.244.0/22 168.160.0.0/17 168.160.152.0/24 168.160.158.0/23 168.160.160.0/21 168.160.168.0/24 +169.40.44.0/24 171.8.0.0/13 171.34.0.0/15 171.36.0.0/14 @@ -2820,7 +2941,7 @@ 171.112.0.0/12 171.208.0.0/12 172.81.192.0/18 -173.39.200.0/23 +174.136.239.0/24 175.0.0.0/12 175.16.0.0/13 175.24.0.0/15 @@ -2830,15 +2951,18 @@ 175.44.0.0/16 175.46.0.0/15 175.102.0.0/19 -175.102.128.0/21 +175.102.128.0/20 +175.102.144.0/21 +175.102.152.0/24 175.102.178.0/23 175.102.180.0/22 -175.102.184.0/24 +175.102.184.0/22 +175.102.188.0/23 +175.102.190.0/24 175.102.196.0/24 175.146.0.0/15 175.148.0.0/14 175.152.0.0/14 -175.158.112.0/20 175.160.0.0/12 175.178.0.0/16 175.184.128.0/18 @@ -2847,7 +2971,9 @@ 175.188.188.0/22 175.190.24.0/21 175.190.126.0/23 -178.253.252.0/24 +177.0.140.0/24 +178.219.5.0/24 +178.248.72.0/24 180.76.16.0/20 180.76.32.0/19 180.76.64.0/18 @@ -2861,7 +2987,6 @@ 180.89.60.0/23 180.89.64.0/19 180.91.192.0/20 -180.92.176.0/24 180.95.128.0/17 180.96.0.0/11 180.129.128.0/17 @@ -2897,8 +3022,7 @@ 180.188.32.0/20 180.200.252.0/22 180.201.0.0/16 -180.203.0.0/23 -180.203.3.0/24 +180.203.0.0/22 180.203.4.0/24 180.208.0.0/15 180.210.212.0/22 @@ -2946,7 +3070,9 @@ 183.84.0.0/15 183.91.39.0/24 183.91.40.0/21 -183.91.48.0/21 +183.91.48.0/23 +183.91.50.0/24 +183.91.52.0/22 183.91.56.0/24 183.91.61.0/24 183.91.63.0/24 @@ -2958,27 +3084,41 @@ 183.170.0.0/16 183.172.0.0/14 183.184.0.0/13 -183.192.0.0/10 +183.192.0.0/11 +183.224.0.0/12 +183.240.0.0/13 +183.248.0.0/14 +183.252.0.0/15 +183.254.0.0/16 +183.255.0.0/18 +183.255.64.0/19 +183.255.96.0/20 +183.255.112.0/21 +183.255.120.0/22 +183.255.125.0/24 +183.255.126.0/23 +183.255.128.0/18 +183.255.192.0/19 +183.255.224.0/21 +183.255.232.0/23 +183.255.238.0/23 +183.255.240.0/20 185.75.173.0/24 185.75.174.0/24 -185.80.200.0/22 -185.183.84.0/24 -185.194.148.0/24 -185.201.226.0/23 188.131.128.0/17 -192.6.161.0/24 -192.6.243.0/24 +191.217.175.0/24 192.140.160.0/19 192.140.208.0/21 192.144.128.0/17 192.163.11.0/24 192.232.97.0/24 -193.9.44.0/24 -193.9.46.0/24 193.112.0.0/16 +193.233.49.0/24 194.127.229.0/24 194.138.202.0/23 194.138.245.0/24 +194.231.140.0/24 +195.162.246.0/24 198.208.17.0/24 198.208.19.0/24 198.208.30.0/24 @@ -2988,17 +3128,15 @@ 198.208.112.0/23 199.182.239.0/24 199.244.144.0/24 -202.1.86.0/23 -202.1.90.0/23 -202.1.110.0/23 -202.1.112.0/23 +200.181.87.0/24 +201.24.195.0/24 202.4.128.0/19 202.4.252.0/22 202.6.202.0/23 202.14.235.0/24 202.14.236.0/23 202.14.238.0/24 -202.30.124.0/24 +202.38.2.0/23 202.38.8.0/21 202.38.64.0/18 202.38.128.0/23 @@ -3015,8 +3153,11 @@ 202.46.228.0/23 202.46.232.0/23 202.47.104.0/21 -202.51.32.0/20 -202.55.0.0/19 +202.55.0.0/23 +202.55.4.0/24 +202.55.18.0/23 +202.55.20.0/22 +202.55.24.0/21 202.57.192.0/24 202.57.196.0/22 202.57.200.0/23 @@ -3034,15 +3175,14 @@ 202.69.16.0/20 202.72.112.0/20 202.73.128.0/22 -202.73.208.0/20 202.73.240.0/20 202.75.208.0/20 -202.75.248.0/22 +202.75.252.0/22 202.80.192.0/22 202.81.176.0/20 202.84.17.0/24 202.85.208.0/20 -202.89.96.0/24 +202.89.96.0/22 202.89.232.0/21 202.90.20.0/22 202.90.96.0/20 @@ -3054,7 +3194,9 @@ 202.91.240.0/21 202.91.248.0/22 202.93.252.0/22 -202.95.0.0/19 +202.95.0.0/24 +202.95.2.0/23 +202.95.24.0/21 202.96.0.0/13 202.104.0.0/14 202.108.0.0/15 @@ -3076,6 +3218,7 @@ 202.123.106.0/23 202.123.108.0/24 202.123.110.0/23 +202.123.120.0/22 202.125.176.0/20 202.127.0.0/21 202.127.12.0/22 @@ -3103,28 +3246,35 @@ 202.143.16.0/23 202.143.100.0/22 202.148.96.0/19 -202.149.224.0/20 +202.149.224.0/19 202.150.16.0/20 +202.153.7.0/24 202.153.48.0/20 202.158.160.0/19 -202.160.140.0/22 +202.158.242.0/24 +202.160.140.0/23 +202.160.143.0/24 202.164.0.0/20 202.168.160.0/19 202.170.128.0/19 202.170.216.0/21 +202.171.232.0/24 +202.172.7.0/24 +202.173.6.0/24 202.173.8.0/22 202.173.14.0/23 202.173.224.0/19 -202.179.240.0/20 +202.179.240.0/21 202.181.28.0/24 202.181.120.0/21 202.189.3.0/24 202.189.4.0/22 202.189.8.0/21 -202.189.16.0/21 +202.189.16.0/20 +202.189.32.0/20 +202.189.48.0/22 202.192.0.0/12 203.0.104.0/21 -203.0.250.0/23 203.2.64.0/21 203.2.112.0/21 203.2.160.0/21 @@ -3137,6 +3287,9 @@ 203.12.93.0/24 203.13.81.0/24 203.15.0.0/20 +203.16.63.0/24 +203.17.249.0/24 +203.19.1.0/24 203.25.52.0/24 203.25.208.0/20 203.32.48.0/23 @@ -3303,10 +3456,9 @@ 203.95.2.0/24 203.95.4.0/22 203.95.128.0/23 -203.99.21.0/24 -203.99.22.0/23 -203.99.26.0/23 -203.99.30.0/23 +203.99.20.0/22 +203.99.24.0/21 +203.100.60.0/24 203.100.92.0/22 203.100.192.0/20 203.104.32.0/20 @@ -3322,8 +3474,6 @@ 203.107.80.0/21 203.107.96.0/23 203.107.116.0/22 -203.107.120.0/22 -203.107.124.0/23 203.110.160.0/19 203.110.208.0/20 203.110.232.0/23 @@ -3342,10 +3492,11 @@ 203.119.176.0/20 203.119.192.0/18 203.129.12.0/22 +203.130.32.0/22 203.130.40.0/21 203.132.32.0/19 -203.133.240.0/21 203.135.96.0/19 +203.142.219.0/24 203.148.0.0/18 203.148.64.0/20 203.148.86.0/23 @@ -3364,6 +3515,7 @@ 203.168.0.0/19 203.170.58.0/23 203.174.4.0/24 +203.174.6.0/24 203.174.96.0/19 203.175.128.0/19 203.176.92.0/22 @@ -3372,10 +3524,13 @@ 203.187.160.0/19 203.189.0.0/23 203.189.6.0/23 +203.189.115.0/24 203.189.192.0/19 203.189.240.0/22 203.190.100.0/23 203.191.0.0/23 +203.191.2.0/24 +203.191.7.0/24 203.191.144.0/21 203.192.1.0/24 203.192.2.0/23 @@ -3392,9 +3547,9 @@ 203.195.118.0/23 203.195.128.0/17 203.196.0.0/21 +203.201.181.0/24 203.202.236.0/23 -203.205.88.0/23 -203.205.90.0/24 +203.205.88.0/24 203.207.64.0/19 203.207.96.0/21 203.207.104.0/22 @@ -3406,22 +3561,18 @@ 203.207.224.0/19 203.208.32.0/19 203.209.224.0/19 -203.212.0.0/21 -203.212.8.0/22 +203.212.0.0/20 203.212.80.0/23 203.215.232.0/23 203.215.236.0/24 -203.223.17.0/24 -203.223.19.0/24 -203.223.20.0/23 -204.77.130.0/24 -204.235.241.0/24 -205.198.19.0/24 +203.223.21.0/24 206.109.75.0/24 210.2.0.0/23 210.2.4.0/24 210.5.0.0/19 -210.5.56.0/21 +210.5.56.0/24 +210.5.58.0/23 +210.5.60.0/22 210.5.128.0/19 210.12.0.0/16 210.13.0.0/17 @@ -3454,17 +3605,15 @@ 210.26.0.0/15 210.28.0.0/14 210.32.0.0/12 -210.48.136.0/21 +210.48.140.0/23 +210.48.142.0/24 210.51.0.0/16 210.52.0.0/15 210.72.0.0/17 210.72.128.0/18 210.72.192.0/19 210.72.224.0/20 -210.72.240.0/23 -210.72.243.0/24 -210.72.245.0/24 -210.72.246.0/24 +210.72.240.0/21 210.73.0.0/17 210.73.128.0/18 210.73.192.0/22 @@ -3493,16 +3642,12 @@ 210.77.0.0/18 210.77.64.0/19 210.77.128.0/19 -210.77.176.0/22 -210.77.180.0/23 -210.77.182.0/24 -210.77.184.0/24 -210.77.189.0/24 -210.77.190.0/23 +210.77.176.0/20 210.77.192.0/18 210.78.128.0/24 210.78.134.0/23 210.78.139.0/24 +210.78.144.0/23 210.79.224.0/19 210.82.0.0/15 210.87.128.0/18 @@ -3556,10 +3701,7 @@ 211.98.0.0/16 211.99.8.0/21 211.99.16.0/23 -211.99.25.0/24 -211.99.26.0/24 -211.99.28.0/22 -211.99.32.0/19 +211.99.24.0/21 211.99.64.0/18 211.99.128.0/18 211.99.192.0/19 @@ -3571,7 +3713,9 @@ 211.101.232.0/21 211.101.240.0/21 211.102.80.0/20 -211.102.128.0/18 +211.102.128.0/21 +211.102.144.0/21 +211.102.160.0/20 211.102.192.0/23 211.102.208.0/20 211.102.240.0/21 @@ -3661,38 +3805,32 @@ 211.159.64.0/21 211.159.128.0/17 211.160.0.0/22 -211.160.4.0/24 -211.160.9.0/24 -211.160.10.0/23 +211.160.4.0/23 +211.160.8.0/22 211.160.14.0/23 -211.160.16.0/24 -211.160.18.0/23 -211.160.20.0/23 -211.160.22.0/24 -211.160.24.0/24 -211.160.27.0/24 +211.160.16.0/21 +211.160.24.0/22 211.160.28.0/23 211.160.36.0/23 -211.160.46.0/24 -211.160.62.0/24 +211.160.46.0/23 +211.160.62.0/23 211.160.72.0/21 211.160.80.0/20 211.160.112.0/23 211.160.115.0/24 211.160.120.0/24 211.160.160.0/19 -211.160.195.0/24 +211.160.194.0/23 211.160.196.0/24 -211.160.203.0/24 -211.160.205.0/24 -211.160.244.0/24 -211.160.246.0/23 -211.160.248.0/21 +211.160.202.0/23 +211.160.204.0/23 +211.160.240.0/20 211.161.0.0/20 211.161.24.0/22 211.161.32.0/20 211.161.60.0/23 211.161.62.0/24 +211.161.97.0/24 211.161.101.0/24 211.161.102.0/23 211.161.120.0/21 @@ -3713,9 +3851,25 @@ 211.167.176.0/20 211.167.224.0/19 212.64.0.0/17 -212.103.62.0/24 212.129.128.0/17 -218.0.0.0/11 +212.134.136.0/24 +212.134.159.0/24 +212.189.96.0/24 +218.0.0.0/12 +218.16.0.0/13 +218.24.0.0/14 +218.28.0.0/15 +218.30.0.0/19 +218.30.32.0/21 +218.30.40.0/22 +218.30.45.0/24 +218.30.46.0/23 +218.30.48.0/21 +218.30.56.0/22 +218.30.60.0/23 +218.30.64.0/18 +218.30.128.0/17 +218.31.0.0/16 218.56.0.0/13 218.64.0.0/11 218.96.0.0/24 @@ -3738,7 +3892,9 @@ 218.98.111.0/24 218.98.112.0/20 218.98.160.0/24 -218.98.176.0/24 +218.98.176.0/22 +218.98.180.0/23 +218.98.182.0/24 218.98.192.0/24 218.100.88.0/21 218.104.0.0/15 @@ -3746,15 +3902,18 @@ 218.107.0.0/18 218.107.128.0/17 218.108.0.0/15 -218.185.240.0/21 +218.185.240.0/24 +218.185.242.0/24 +218.185.244.0/24 +218.185.246.0/23 218.192.0.0/12 -218.213.85.0/24 -218.240.0.0/19 +218.240.0.0/18 218.240.128.0/19 218.240.160.0/21 -218.240.168.0/24 +218.240.168.0/22 218.240.176.0/20 218.241.16.0/21 +218.241.24.0/22 218.241.96.0/20 218.241.112.0/22 218.241.116.0/23 @@ -3782,16 +3941,11 @@ 218.246.160.0/19 218.246.192.0/19 218.247.0.0/18 -218.247.64.0/19 218.247.128.0/17 218.249.0.0/16 -218.252.0.0/17 -218.253.0.0/18 219.72.0.0/16 219.82.0.0/16 219.83.160.0/19 -219.100.148.0/22 -219.117.16.0/20 219.128.0.0/12 219.144.0.0/14 219.148.0.0/15 @@ -3814,8 +3968,6 @@ 219.232.160.0/19 219.232.192.0/19 219.233.0.0/16 -219.234.0.0/21 -219.234.16.0/20 219.234.80.0/20 219.234.96.0/19 219.234.128.0/17 @@ -3836,7 +3988,7 @@ 219.236.0.0/14 219.242.0.0/15 219.244.0.0/14 -220.101.192.0/19 +220.101.192.0/18 220.112.0.0/22 220.112.8.0/24 220.112.40.0/21 @@ -3859,7 +4011,21 @@ 220.154.80.0/22 220.154.128.0/20 220.154.147.0/24 -220.160.0.0/12 +220.160.0.0/13 +220.168.0.0/14 +220.172.0.0/15 +220.174.0.0/20 +220.174.16.0/21 +220.174.24.0/23 +220.174.28.0/22 +220.174.32.0/19 +220.174.64.0/20 +220.174.80.0/21 +220.174.88.0/22 +220.174.92.0/23 +220.174.96.0/19 +220.174.128.0/17 +220.175.0.0/16 220.176.0.0/14 220.180.0.0/16 220.181.0.0/17 @@ -3892,6 +4058,7 @@ 220.203.0.0/17 220.203.128.0/18 220.203.216.0/23 +220.203.220.0/22 220.203.224.0/19 220.205.0.0/16 220.206.192.0/22 @@ -3900,17 +4067,12 @@ 220.207.176.0/22 220.207.184.0/22 220.207.254.0/23 -220.231.32.0/20 +220.231.0.0/18 220.231.128.0/17 220.232.64.0/18 220.234.0.0/16 220.242.180.0/24 -220.243.128.0/20 -220.243.153.0/24 -220.243.154.0/23 -220.243.156.0/23 -220.243.158.0/24 -220.243.176.0/20 +220.243.128.0/18 220.248.0.0/16 220.249.0.0/17 220.249.128.0/18 @@ -3936,7 +4098,8 @@ 221.133.235.0/24 221.133.244.0/23 221.137.0.0/16 -221.174.0.0/17 +221.172.0.0/16 +221.174.0.0/16 221.176.0.0/13 221.192.0.0/14 221.196.0.0/15 @@ -3957,34 +4120,9 @@ 222.59.128.0/17 222.64.0.0/11 222.125.0.0/16 -222.126.128.0/22 -222.126.132.0/23 -222.126.134.0/24 -222.126.138.0/23 -222.126.140.0/22 -222.126.144.0/24 -222.126.146.0/23 -222.126.148.0/22 -222.126.152.0/21 -222.126.160.0/20 -222.126.176.0/21 -222.126.184.0/22 -222.126.188.0/23 -222.126.192.0/22 -222.126.196.0/23 -222.126.200.0/22 -222.126.208.0/23 -222.126.212.0/22 -222.126.216.0/23 -222.126.228.0/23 -222.126.234.0/23 -222.126.236.0/22 -222.126.240.0/20 +222.126.128.0/17 222.128.0.0/12 222.160.0.0/14 -222.166.96.0/20 -222.166.160.0/20 -222.166.240.0/20 222.168.0.0/13 222.176.0.0/12 222.192.0.0/11 @@ -4002,9 +4140,25 @@ 223.2.0.0/15 223.4.0.0/14 223.8.0.0/13 -223.26.0.0/24 223.27.184.0/22 -223.64.0.0/10 +223.64.0.0/11 +223.96.0.0/12 +223.112.0.0/14 +223.116.0.0/15 +223.121.128.0/17 +223.122.4.0/22 +223.122.12.0/22 +223.122.18.0/23 +223.122.20.0/22 +223.122.26.0/23 +223.122.28.0/22 +223.123.195.0/24 +223.123.196.0/22 +223.123.202.0/23 +223.123.204.0/22 +223.123.208.0/20 +223.123.224.0/19 +223.124.0.0/14 223.128.0.0/15 223.144.0.0/12 223.160.0.0/20 @@ -4024,9 +4178,6 @@ 223.161.200.0/21 223.161.208.0/20 223.161.224.0/19 -223.165.10.0/24 -223.165.12.0/24 -223.165.80.0/20 223.166.0.0/15 223.192.0.0/15 223.198.0.0/15 @@ -4034,7 +4185,7 @@ 223.202.67.0/24 223.202.131.0/24 223.202.132.0/24 -223.202.135.0/24 +223.202.134.0/23 223.202.211.0/24 223.202.212.0/24 223.203.3.0/24 @@ -4050,7 +4201,6 @@ 223.240.0.0/13 223.248.0.0/14 223.252.194.0/24 -223.252.196.0/24 223.252.199.0/24 223.252.200.0/23 223.252.202.0/24 diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.ver b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.ver index 0593cbd7b..e1bc5b0fe 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.ver +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip4.ver @@ -1 +1 @@ -20260704060757 +202606132337 diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.txt b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.txt index 1b668724e..0f3ead7f8 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.txt +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.txt @@ -1,6 +1,8 @@ 2001:250::/30 -2001:254::/31 +2001:254::/33 +2001:255::/32 2001:256:100::/48 +2001:678:53c::/48 2001:c68::/32 2001:cc0::/32 2001:da8::/32 @@ -11,8 +13,11 @@ 2001:dc7::/32 2001:dd8:1::/48 2001:dd9::/48 +2001:df5:4740::/48 2001:df6:40::/48 2001:4510:400::/40 +2001:4510:1480::/41 +2001:4511:1480::/41 2400:1160::/32 2400:3200::/32 2400:5280:f803::/48 @@ -20,6 +25,8 @@ 2400:6000::/32 2400:6600::/32 2400:6e60:1301::/48 +2400:7100::/32 +2400:73e0::/32 2400:75aa::/32 2400:7fc0::/40 2400:7fc0:220::/44 @@ -36,9 +43,14 @@ 2400:8200::/32 2400:89c0:1010::/44 2400:89c0:1050::/46 +2400:89c0:1130::/44 2400:89c0:1150::/48 +2400:89c0:2100::/48 +2400:89c0:2200::/48 +2400:89c0:3010::/44 2400:89c0:6000::/48 -2400:8fc0::/32 +2400:89c0:6100::/48 +2400:8fc0:571::/48 2400:9020:f010::/46 2400:9380:8001::/48 2400:9380:8003::/48 @@ -85,15 +97,19 @@ 2400:cb80:e40::/44 2400:da00::/32 2400:dd00::/28 -2400:ee00:ffec::/46 -2400:ee00:fff0::/44 +2400:e680::/32 +2400:ee00::/32 2400:f6e0::/32 2400:f720::/32 2400:fe00::/32 +2401:20::/40 +2401:20:1330::/46 2401:800::/32 2401:1160::/32 +2401:1200::/48 2401:1320::/32 2401:1d40::/32 +2401:2780::/32 2401:2a00:f000::/43 2401:2e00::/32 2401:3480::/36 @@ -101,11 +117,39 @@ 2401:3480:3000::/36 2401:34a0::/31 2401:3800::/32 +2401:3880::/32 +2401:3980::/32 +2401:3a80::/32 +2401:3b80::/32 +2401:3c80::/32 +2401:3d80::/32 +2401:3e80::/32 +2401:3f80::/32 +2401:4180::/32 +2401:4280::/32 +2401:4380::/32 +2401:4480::/32 +2401:4580::/32 +2401:4680::/32 +2401:4780::/32 +2401:4880::/32 +2401:4a80::/32 2401:5c20:10::/48 2401:71c0::/48 2401:7660::/48 +2401:7700::/32 +2401:7d40::/32 2401:7e00::/32 2401:8be0::/48 +2401:8d00::/47 +2401:8d00:3::/48 +2401:8d00:4::/47 +2401:8d00:7::/48 +2401:8d00:8::/47 +2401:8d00:b::/48 +2401:8d00:c::/48 +2401:8d00:f::/48 +2401:8d00:12::/48 2401:9a00::/44 2401:9a00:10::/46 2401:a140:1::/48 @@ -121,26 +165,56 @@ 2401:c020:6::/48 2401:c020:8::/47 2401:c020:14::/48 +2401:ca00::/32 2401:cb80::/32 2401:cc00::/32 2401:ce00::/32 2401:d0e0:3::/48 +2401:d180::/46 +2401:d180:10::/47 +2401:d180:111::/48 +2401:d180:2120::/48 +2401:d780::/32 2401:d920::/48 2401:de00::/32 2401:ec00::/32 2401:fa00:40::/48 2402:840:d000::/46 2402:840:e000::/46 +2402:840:f000::/38 2402:c60::/48 2402:1440::/32 +2402:18a0::/32 2402:2000::/32 +2402:2e60::/39 +2402:34e0::/32 2402:36e0::/44 +2402:3c00::/32 +2402:3f80:1400::/40 +2402:4440:9000::/39 +2402:4440:9200::/48 +2402:4440:9400::/48 +2402:4440:9600::/48 +2402:4440:9800::/48 +2402:4440:9810::/48 +2402:4440:9900::/48 +2402:4440:9a00::/48 +2402:4440:9b00::/48 +2402:4440:9c00::/48 +2402:4440:9d00::/48 +2402:4440:9e00::/48 +2402:4440:a000::/39 +2402:4440:a800::/46 +2402:4440:a810::/46 2402:4b80::/32 2402:4e00::/32 +2402:5e40::/32 2402:5ec0::/32 2402:6e80::/32 2402:6f40::/48 2402:6f40:2::/47 +2402:6fc0::/48 +2402:7040::/32 2402:7d80::/48 2402:7d80:240::/48 2402:7d80:8888::/48 @@ -162,7 +236,12 @@ 2402:dfc0:50::/44 2402:e480::/32 2402:e740::/32 -2402:e7c0::/32 +2402:e7c0::/37 +2402:e7c0:800::/38 +2402:e7c0:1000::/36 +2402:e7c0:2000::/35 +2402:e7c0:4000::/34 +2402:e7c0:8000::/33 2402:e880::/48 2402:ef40::/32 2402:f000::/32 @@ -190,6 +269,7 @@ 2403:7580::/32 2403:8080:101::/48 2403:8c00::/32 +2403:9b00:2000::/48 2403:9b00:2400::/48 2403:9b00:6000::/48 2403:a100::/48 @@ -251,6 +331,10 @@ 2404:c940::/48 2404:e280::/47 2404:e8c0::/32 +2404:f4c0:f000::/48 +2404:f4c0:f003::/48 +2404:f4c0:f004::/46 +2404:f4c0:f008::/45 2405:80:10::/48 2405:80:13::/48 2405:6c0:2::/48 @@ -270,24 +354,130 @@ 2405:6f00:c602::/48 2405:7040:6000::/47 2405:78c0:6e00::/43 +2405:8280::/32 2405:8a40::/32 2405:a900:ffee::/48 +2405:a900:fffe::/48 2405:ad00::/32 2405:b7c0::/32 2405:d900::/32 2405:e000::/32 +2405:e600:2::/48 2405:f940::/32 2406:280::/32 -2406:840:9680::/44 +2406:840:8100::/40 +2406:840:9178::/48 +2406:840:9962::/47 +2406:840:9966::/48 +2406:840:996c::/48 +2406:840:e080::/44 +2406:840:e0cf::/48 +2406:840:e0e0::/46 +2406:840:e0e4::/47 +2406:840:e10f::/48 +2406:840:e14f::/48 +2406:840:e330::/44 +2406:840:e600::/44 +2406:840:e620::/47 +2406:840:e80f::/48 +2406:840:eb01::/48 +2406:840:eb02::/47 +2406:840:eb04::/47 +2406:840:eb07::/48 +2406:840:eb08::/48 +2406:840:eb0b::/48 +2406:840:ee40::/47 +2406:840:ee44::/48 +2406:840:ee4b::/48 +2406:840:ee4d::/48 +2406:840:eee5::/48 +2406:840:f380::/44 +2406:840:f44f::/48 2406:840:f889::/48 +2406:840:fa01::/48 +2406:840:fa60::/44 +2406:840:fc10::/47 +2406:840:fc12::/48 +2406:840:fc20::/47 +2406:840:fc22::/48 +2406:840:fc30::/47 +2406:840:fc32::/48 +2406:840:fc40::/47 +2406:840:fc42::/48 +2406:840:fc50::/47 +2406:840:fc52::/48 +2406:840:fc60::/47 +2406:840:fc62::/48 +2406:840:fc70::/47 +2406:840:fc72::/48 +2406:840:fc90::/47 +2406:840:fc92::/48 +2406:840:fca0::/47 +2406:840:fca2::/48 +2406:840:fcb0::/47 +2406:840:fcb2::/48 +2406:840:fcd0::/48 +2406:840:fd40::/48 +2406:840:fd46::/47 +2406:840:fd48::/48 +2406:840:fd50::/48 +2406:840:fd56::/47 +2406:840:fd58::/48 +2406:840:fd60::/48 +2406:840:fd66::/47 +2406:840:fd68::/48 +2406:840:fd70::/48 +2406:840:fd76::/47 +2406:840:fd78::/48 +2406:840:fd80::/48 +2406:840:fd86::/47 +2406:840:fd88::/48 +2406:840:fd90::/47 +2406:840:fd92::/48 +2406:840:fd9f::/48 +2406:840:fda0::/48 +2406:840:fda7::/48 +2406:840:fdb0::/48 +2406:840:fdb7::/48 +2406:840:fdc0::/48 +2406:840:fdc7::/48 +2406:840:fe90::/46 +2406:840:fe94::/48 +2406:840:fe96::/47 +2406:840:fe98::/46 +2406:840:fe9d::/48 +2406:840:fe9e::/48 +2406:840:fea2::/47 +2406:840:fea4::/46 +2406:840:fea8::/48 +2406:840:feaa::/47 +2406:840:feac::/46 +2406:840:fec0::/48 +2406:840:fec2::/47 +2406:840:fec4::/48 +2406:840:feca::/47 +2406:840:fecf::/48 +2406:840:fed1::/48 +2406:840:fed8::/48 +2406:840:fedb::/48 +2406:840:fedc::/48 +2406:840:fef3::/48 +2406:840:fef4::/48 +2406:840:fef6::/47 +2406:840:fef8::/46 +2406:840:fefc::/48 +2406:840:feff::/48 2406:1080::/48 +2406:1e40:f012::/47 2406:2700::/32 2406:3340::/32 +2406:3640:1::/48 2406:4d00::/48 2406:52c0::/32 2406:5340:6666::/48 2406:5340:8888::/48 2406:6100::/32 +2406:8880::/48 2406:b640:100::/48 2406:b640:4100::/48 2406:cac0::/40 @@ -304,6 +494,7 @@ 2407:23c0::/48 2407:2840::/48 2407:37c0::/32 +2407:4980::/32 2407:5380::/32 2407:6c40:1100::/48 2407:6c40:1210::/48 @@ -322,7 +513,51 @@ 2407:c080:6000::/36 2407:c080:8000::/36 2407:d9c0::/32 -2408:4000::/22 +2408:4000::/40 +2408:4000:100::/47 +2408:4000:103::/48 +2408:4000:104::/46 +2408:4000:108::/45 +2408:4000:110::/44 +2408:4000:120::/43 +2408:4000:140::/42 +2408:4000:180::/41 +2408:4000:200::/39 +2408:4000:400::/38 +2408:4000:800::/37 +2408:4000:1000::/36 +2408:4000:2000::/35 +2408:4000:4000::/34 +2408:4000:8000::/33 +2408:4001::/32 +2408:4002::/31 +2408:4004::/30 +2408:4008::/29 +2408:4010::/30 +2408:4014::/31 +2408:4016:1::/48 +2408:4016:2::/47 +2408:4016:4::/46 +2408:4016:8::/45 +2408:4016:10::/44 +2408:4016:20::/43 +2408:4016:40::/42 +2408:4016:80::/41 +2408:4016:100::/40 +2408:4016:200::/39 +2408:4016:400::/38 +2408:4016:800::/37 +2408:4016:1000::/36 +2408:4016:2000::/35 +2408:4016:4000::/34 +2408:4016:8000::/33 +2408:4017::/32 +2408:4018::/29 +2408:4020::/27 +2408:4040::/26 +2408:4080::/25 +2408:4100::/24 +2408:4200::/23 2408:8000::/48 2408:8000:2::/47 2408:8000:1000::/36 @@ -579,103 +814,86 @@ 2408:840c:dd00::/40 2408:840c:de00::/40 2408:840d::/42 -2408:840d:40::/43 2408:840d:200::/42 2408:840d:400::/42 2408:840d:440::/43 2408:840d:600::/42 2408:840d:d00::/42 -2408:840d:d40::/43 2408:840d:f00::/42 2408:840d:1100::/42 2408:840d:1140::/43 2408:840d:1300::/42 2408:840d:1a00::/42 -2408:840d:1a40::/43 2408:840d:1c00::/42 2408:840d:1e00::/42 2408:840d:1e40::/43 2408:840d:2000::/42 2408:840d:2700::/42 -2408:840d:2740::/43 2408:840d:2900::/42 2408:840d:2b00::/42 2408:840d:2b40::/43 2408:840d:2d00::/42 2408:840d:3400::/42 -2408:840d:3440::/43 2408:840d:3600::/42 2408:840d:3800::/42 2408:840d:3840::/43 2408:840d:3a00::/42 2408:840d:4e00::/42 -2408:840d:4e40::/43 2408:840d:5000::/42 2408:840d:5200::/42 2408:840d:5240::/43 2408:840d:5400::/42 2408:840d:5b00::/42 -2408:840d:5b40::/43 2408:840d:5d00::/42 2408:840d:5f00::/42 2408:840d:5f40::/43 2408:840d:6100::/42 2408:840d:6800::/42 -2408:840d:6840::/43 2408:840d:6a00::/42 2408:840d:6c00::/42 2408:840d:6c40::/43 2408:840d:6e00::/42 2408:840d:7500::/42 -2408:840d:7540::/43 2408:840d:7700::/42 2408:840d:7900::/42 2408:840d:7940::/43 2408:840d:7b00::/42 2408:840d:8200::/42 -2408:840d:8240::/43 2408:840d:8400::/42 2408:840d:8600::/42 2408:840d:8640::/43 2408:840d:8800::/42 2408:840d:8f00::/42 -2408:840d:8f40::/43 2408:840d:9100::/42 2408:840d:9300::/42 2408:840d:9340::/43 2408:840d:9500::/42 2408:840d:9c00::/42 -2408:840d:9c40::/43 2408:840d:9e00::/42 2408:840d:a000::/42 2408:840d:a040::/43 2408:840d:a200::/42 2408:840d:a900::/42 -2408:840d:a940::/43 2408:840d:ab00::/42 2408:840d:ad00::/42 2408:840d:ad40::/43 2408:840d:af00::/42 2408:840d:b600::/42 -2408:840d:b640::/43 2408:840d:b800::/42 2408:840d:ba00::/42 2408:840d:ba40::/43 2408:840d:bc00::/42 2408:840d:c300::/42 -2408:840d:c340::/43 2408:840d:c500::/42 2408:840d:c700::/42 2408:840d:c740::/43 2408:840d:c900::/42 2408:840d:d000::/42 -2408:840d:d040::/43 2408:840d:d200::/42 2408:840d:d400::/42 2408:840d:d440::/43 2408:840d:d600::/42 2408:840d:dd00::/42 -2408:840d:dd40::/43 2408:840d:de00::/42 2408:840e:dd00::/40 2408:840e:de00::/40 @@ -856,7 +1074,6 @@ 2408:8a06:100::/47 2408:8a07::/40 2408:8a21:4000::/35 -2408:8a22::/35 2408:8a23:4000::/34 2408:8a24:4000::/34 2408:8a26:c000::/34 @@ -870,14 +1087,47 @@ 2409:27fc::/48 2409:27fe::/33 2409:6100::/44 -2409:8000::/20 +2409:8000::/21 +2409:8800::/22 +2409:8c00::/25 +2409:8c80::/30 +2409:8c84::/32 +2409:8c85::/34 +2409:8c85:4000::/36 +2409:8c85:5000::/38 +2409:8c85:5400::/43 +2409:8c85:5420::/44 +2409:8c85:5430::/47 +2409:8c85:5433::/48 +2409:8c85:5434::/46 +2409:8c85:5438::/45 +2409:8c85:5440::/42 +2409:8c85:5480::/41 +2409:8c85:5500::/40 +2409:8c85:5600::/39 +2409:8c85:5800::/37 +2409:8c85:6000::/35 +2409:8c85:8000::/33 +2409:8c86::/31 +2409:8c88::/29 +2409:8c90::/28 +2409:8ca0::/27 +2409:8cc0::/26 +2409:8d00::/24 +2409:8e00::/23 240a:2000::/40 240a:2001:100::/40 240a:2001:1000::/36 +240a:4002:1b00::/40 +240a:4010:8000::/33 240a:4020:83a::/48 240a:4020:883a::/48 240a:4021:83a::/48 240a:4021:883a::/48 +240a:4080:8000::/35 +240a:4084:2000::/35 +240a:4088:a000::/35 +240a:408c:2000::/35 240a:4090:50::/48 240a:4090:120::/48 240a:4090:250::/48 @@ -892,11 +1142,20 @@ 240a:4090:5200::/40 240a:4090:7000::/39 240a:4090:7200::/40 +240a:4090:8000::/34 +240a:4093::/35 +240a:4094:2000::/35 +240a:409c:2000::/35 +240a:40a4:2000::/35 +240a:40a8:8000::/35 +240a:40ac:2000::/35 240a:40b0:83a::/48 240a:40b0:283a::/48 240a:40b0:483a::/48 240a:40b0:683a::/48 240a:40c0:8000::/43 +240a:40c0:8200::/48 +240a:40c0:8240::/48 240a:40c0:a000::/43 240a:40c0:c000::/43 240a:40c0:e000::/43 @@ -923,10 +1182,31 @@ 240a:40c3:8000::/43 240a:40c3:a000::/43 240a:40c3:c000::/43 +240a:40c3:c200::/48 +240a:40c3:c240::/48 240a:40c3:e000::/43 240a:40c4::/43 240a:40c4:2000::/43 240a:40c4:4000::/43 +240a:4172::/31 +240a:4180::/31 +240a:41b0::/34 +240a:41b8:8800::/40 +240a:41b8:a000::/40 +240a:41b8:d400::/40 +240a:41b8:e400::/40 +240a:41d6::/31 +240a:41f2::/31 +240a:4208::/30 +240a:4224:9000::/44 +240a:4224:a000::/44 +240a:4224:c000::/44 +240a:4224:d000::/44 +240a:4224:e000::/44 +240a:4225:1000::/44 +240a:4225:3000::/44 +240a:4230::/31 +240a:4242::/31 240a:4280::/26 240a:42c0::/27 240a:42e0::/28 @@ -943,7 +1223,69 @@ 240c:4000::/22 240c:c000::/20 240d:4000::/21 -240e::/20 +240e::/21 +240e:800::/24 +240e:900::/26 +240e:940::/27 +240e:960::/28 +240e:970::/29 +240e:978::/30 +240e:97c::/32 +240e:97d::/33 +240e:97d:8001::/48 +240e:97d:8002::/47 +240e:97d:8004::/46 +240e:97d:8008::/45 +240e:97d:8010::/44 +240e:97d:8020::/43 +240e:97d:8040::/42 +240e:97d:8080::/41 +240e:97d:8100::/40 +240e:97d:8200::/39 +240e:97d:8400::/38 +240e:97d:8800::/37 +240e:97d:9000::/36 +240e:97d:a000::/35 +240e:97d:c000::/34 +240e:97e::/31 +240e:980::/31 +240e:982::/32 +240e:983::/35 +240e:983:2000::/44 +240e:983:2011::/48 +240e:983:2012::/48 +240e:983:2014::/46 +240e:983:2018::/45 +240e:983:2021::/48 +240e:983:2023::/48 +240e:983:2024::/46 +240e:983:2028::/45 +240e:983:2031::/48 +240e:983:2032::/47 +240e:983:2034::/46 +240e:983:2038::/45 +240e:983:2040::/42 +240e:983:2080::/41 +240e:983:2100::/40 +240e:983:2200::/39 +240e:983:2400::/38 +240e:983:2800::/37 +240e:983:3000::/36 +240e:983:4000::/34 +240e:983:8000::/33 +240e:984::/30 +240e:988::/29 +240e:990::/28 +240e:9a0::/27 +240e:9c0::/26 +240e:a00::/23 +240e:c00::/22 +2602:f92a:a468::/48 +2602:f92a:a46d::/48 +2602:f92a:a47d::/48 +2602:f9ba:a8::/48 +2602:f9ba:10c::/48 +2602:f9f6:450::/48 2605:9d80:8001::/48 2605:9d80:8011::/48 2605:9d80:8021::/48 @@ -959,6 +1301,8 @@ 2605:9d80:9092::/48 2804:1e48:9001::/48 2804:1e48:9002::/48 +2a01:e281:a400::/48 +2a01:e281:a410::/44 2a04:3e00:1002::/48 2a04:f580:8010::/47 2a04:f580:8090::/48 @@ -983,26 +1327,163 @@ 2a04:f580:9270::/48 2a04:f580:9280::/48 2a04:f580:9290::/48 -2a0a:d685:1ff::/48 -2a0a:d685:300::/40 -2a0b:4340:90::/48 -2a0b:4340:97::/48 -2a0d:c7c7:400::/38 -2a0e:aa06:49f::/48 +2a05:1087::/32 +2a06:3600:e000::/40 +2a06:3600:fb00::/40 +2a06:3600:fc00::/38 +2a06:3605::/32 +2a06:3606::/31 +2a06:a005:260::/43 +2a06:a005:280::/43 +2a06:a005:2a0::/44 +2a07:54c1:2200::/46 +2a07:54c1:2205::/48 +2a0a:d681:e000::/40 +2a0a:d681:fb00::/40 +2a0a:d681:fc00::/38 +2a0a:d687:f001::/48 +2a0c:b641:571::/48 +2a0e:aa06::/40 +2a0e:aa06:440::/48 +2a0e:aa06:450::/44 +2a0e:aa06:490::/44 +2a0e:aa06:4e0::/44 +2a0e:aa07:e144::/48 +2a0e:aa07:e146::/48 +2a0e:aa07:e148::/48 +2a0e:aa07:e280::/47 +2a0e:aa07:e284::/46 +2a0e:aa07:e288::/47 +2a0e:aa07:e28a::/48 +2a0e:aa07:e28d::/48 +2a0e:aa07:e28e::/48 +2a0e:aa07:f0d0::/46 +2a0e:aa07:f0d4::/47 +2a0e:aa07:f0d8::/48 +2a0e:aa07:f0de::/47 2a0e:b107:740::/44 -2a0e:b107:2440::/44 -2a0f:1cc5:1600::/44 -2a0f:1cc5:4700::/40 -2a12:cb41:1200::/44 -2a12:cb41:1300::/43 -2a12:cb41:13f0::/44 +2a0e:b107:da0::/44 +2a0e:b107:14a0::/44 +2a0f:1cc5:14::/46 +2a0f:1cc5:1f::/48 +2a0f:1cc5:40::/48 +2a0f:1cc5:110::/44 +2a0f:1cc5:130::/44 +2a0f:1cc5:140::/42 +2a0f:1cc5:600::/47 +2a0f:1cc5:603::/48 +2a0f:1cc5:642::/48 +2a0f:1cc5:661::/48 +2a0f:1cc5:662::/48 +2a0f:1cc5:6a0::/48 +2a0f:1cc5:f01::/48 +2a0f:1cc5:f02::/47 +2a0f:1cc5:f04::/46 +2a0f:1cc5:f09::/48 +2a0f:1cc5:fff::/48 +2a0f:1cc5:1000::/48 +2a0f:1cc5:1010::/44 +2a0f:1cc5:1032::/47 +2a0f:1cc5:1034::/47 +2a0f:1cc5:1c00::/47 +2a0f:1cc5:1c02::/48 +2a0f:1cc5:1c20::/48 +2a0f:1cc5:1c30::/48 +2a0f:1cc5:1cc0::/48 +2a0f:1cc5:1d10::/47 +2a0f:1cc5:2000::/40 +2a0f:1cc5:2510::/44 +2a0f:1cc5:2520::/44 +2a0f:1cc5:25ff::/48 +2a0f:1cc5:2600::/41 +2a0f:1cc5:2680::/42 +2a0f:1cc5:2d01::/48 +2a0f:1cc5:2d03::/48 +2a0f:1cc5:3200::/46 +2a0f:1cc5:3222::/48 +2a0f:1cc6:b110::/47 +2a0f:1cc6:b210::/47 +2a0f:1cc6:b212::/48 +2a0f:2706::/32 +2a0f:85c1:bfe::/48 +2a12:cb41::/44 +2a12:cb46::/36 +2a12:cb47:ffff::/48 +2a13:1800::/48 +2a13:1800:10::/48 +2a13:1800:80::/44 +2a13:1800:300::/44 2a13:1801:180::/43 -2a13:a5c3:f000::/40 -2a14:7580:d00::/43 -2a14:7580:d20::/46 -2a14:7580:d41::/48 -2a14:7580:d42::/47 -2a14:7580:dff::/48 +2a13:1801:200::/44 +2a13:8c87::/32 +2a13:a5c3:ff41::/48 +2a13:a5c3:ff50::/44 +2a14:67c1:70::/48 +2a14:67c1:74::/48 +2a14:67c1:a110::/44 +2a14:67c1:a123::/48 +2a14:67c1:a125::/48 +2a14:67c1:a128::/48 +2a14:67c1:b500::/48 +2a14:67c1:b511::/48 +2a14:67c1:b514::/48 +2a14:67c1:b530::/44 +2a14:67c1:b563::/48 +2a14:67c1:b578::/48 +2a14:67c1:b586::/47 +2a14:67c1:b588::/47 +2a14:67c1:b590::/48 +2a14:67c1:b599::/48 +2a14:67c1:b5e0::/44 +2a14:67c1:c300::/40 +2a14:67c2:519::/48 +2a14:67c2:a10::/47 +2a14:67c2:a21::/48 +2a14:67c2:a22::/47 +2a14:67c2:a2f::/48 +2a14:67c2:a30::/48 +2a14:67c2:a41::/48 +2a14:67c2:a50::/48 +2a14:67c2:a70::/47 +2a14:67c2:a80::/47 +2a14:67c2:a90::/48 +2a14:67c3:c0::/48 +2a14:67c3:190::/47 +2a14:67c3:192::/48 +2a14:67c3:1100::/47 +2a14:67c3:9990::/44 +2a14:7580:740::/44 +2a14:7580:fffa::/48 +2a14:7581:ffb::/48 +2a14:7581:30b6::/48 +2a14:7583:f224::/47 +2a14:7583:f228::/48 +2a14:7583:f22c::/48 +2a14:7583:f244::/48 +2a14:7583:f264::/48 +2a14:7583:f268::/48 +2a14:7583:f300::/46 +2a14:7583:f304::/47 +2a14:7583:f306::/48 +2a14:7583:f411::/48 +2a14:7583:f4f0::/48 +2a14:7583:f4f4::/48 +2a14:7583:f4fe::/48 +2a14:7583:f701::/48 +2a14:7583:f703::/48 +2a14:7583:f704::/46 +2a14:7583:f708::/46 +2a14:7583:f70c::/48 +2a14:7583:f743::/48 +2a14:7583:f744::/48 +2a14:7583:f764::/48 +2a14:7586:6100::/47 +2a14:7586:6103::/48 +2a14:7586:6104::/47 +2a14:7586:6110::/48 +2a14:7586:6300::/44 +2a14:7dc0:506::/48 +2a14:ae00:50::/44 2c0f:f7a8:8011::/48 2c0f:f7a8:8050::/48 2c0f:f7a8:805f::/48 @@ -1011,4 +1492,5 @@ 2c0f:f7a8:8211::/48 2c0f:f7a8:9020::/48 2c0f:f7a8:9041::/48 +2c0f:f7a8:9211::/48 2c0f:f7a8:9220::/48 diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.ver b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.ver index 0593cbd7b..e1bc5b0fe 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.ver +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_ip6.ver @@ -1 +1 @@ -20260704060757 +202606132337 diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.txt b/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.txt index 0ab202270..f75195134 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.txt +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.txt @@ -1,3 +1,563 @@ +2mdn-cn.net +2mdn.net +a1.mzstatic.com +a2.mzstatic.com +a3.mzstatic.com +a4.mzstatic.com +a5.mzstatic.com +activate.activation-v2.kaspersky.com +activation-v2.geo.kaspersky.com +activation-v2.kaspersky.com +adcdownload.apple.com +adcdownload.apple.com.akadns.net +admob-cn.com +adservice.google.com +afcs.dell.com +ai.zhaomi.cn +alibaba.cdn.steampipe.steamcontent.com +amp-api-edge-lb-cn.itunes-apple.com.akadns.net +amp-api-edge-lb.itunes-apple.com.akadns.net +amp-api-edge.apps.apple.com +amp-api-edge.music.apple.com +amp-api-search-edge.apps.apple.com +amp-api-updates.apps.apple.com +amp-api.apps.apple.com +amp-api.media.apple.com +amp-api.music.apple.com +aod-ssl.itunes.apple.com +aod.itunes.apple.com +api-edge.apps.apple.com +app-analytics-services.com +app-measurement-cn.com +app-measurement.com +app-site-association.cdn-apple.com +appldnld.apple.com +appldnld.g.aaplimg.com +appleid.cdn-apple.com +apps.mzstatic.com +apps5.oingo.com +apptrailers.itunes.apple.com +auth.music.apple.com +avail.googleflights.net +b.c2r.ts.cdn.office.net +bag-cdn.itunes-apple.com.akadns.net +bag.itunes.apple.com +beacons.gcp.gvt2.com +beacons.gvt2.com +beacons2.gvt2.com +beacons3.gvt2.com +bg.v4.a.dl.ws.microsoft.com +bg4.v4.a.dl.ws.microsoft.com +bj1.api.bing.com +bookkeeper.itunes.apple.com +build.microsoft.com +c.admob.com +c.android.clients.google.com +c.pki.goog +cache-management-prod.google.com +cache.pack.google.com +cdn-cn.apple-mapkit.com +cdn-cn1.apple-mapkit.com +cdn-cn2.apple-mapkit.com +cdn-cn3.apple-mapkit.com +cdn-cn4.apple-mapkit.com +cdn.ampproject.org +cdn.apple-mapkit.com +cdn.globalsigncdn.com.cdn.cloudflare.net +cdn.marketplaceimages.windowsphone.com +cdn1.apple-mapkit.com +cdn2.apple-mapkit.com +cdn3.apple-mapkit.com +cdn4.apple-mapkit.com +cds-cdn.v.aaplimg.com +cds.apple.com +cds.apple.com.akadns.net +cdsassets.apple.com +certs-lb.apple.com.akadns.net +certs.apple.com +checkin.gstatic.com +cl1-cdn.origin-apple.com.akadns.net +cl1.apple.com +cl2-cdn.origin-apple.com.akadns.net +cl2-cn.apple.com +cl2.apple.com +cl2.apple.com.edgekey.net.globalredir.akadns.net +cl3-cdn.origin-apple.com.akadns.net +cl3.apple.com +cl4-cdn.origin-apple.com.akadns.net +cl4-cn.apple.com +cl4.apple.com +cl5-cdn.origin-apple.com.akadns.net +cl5.apple.com +clickserve.cc-dt.com +clickserve.dartsearch.net +clickserver.googleads.com +client-api.itunes.apple.com +clientflow.apple.com +clientflow.apple.com.akadns.net +clientperipherals.dell.com +clientservices.googleapis.com +cma.itunes.apple.com +cn-smp-paymentservices.apple.com +cn.download.nvidia.com +cn.widevine.com +cn.windowssearch.com +communities.apple.com +configuration.apple.com +configuration.apple.com.akadns.net +connectivitycheck.gstatic.com +crashlyticsreports-pa.googleapis.com +crl-lb.apple.com.akadns.net +crl.apple.com +crl.globalsign.net +crl.kaspersky.com +crl.pki.goog +crls.pki.goog +csi.gstatic.com +cstat.apple.com +cstat.cdn-apple.com +ctdsb.clouddiffuse.xyz +ctldl.windowsupdate.com +cueme-api.quark.cn +customization-cdn.dell.com +dartsearch-cn.net +dc1-cert.ksn.kaspersky-labs.com +dc1-file.ksn.kaspersky-labs.com +dc1-pp.ksn.kaspersky-labs.com +dc1-st.ksn.kaspersky-labs.com +dc1.ksn.kaspersky-labs.com +dd-cdn.origin-apple.com.akadns.net +dds.dell.com +dejavu.apple.com +devblogs.microsoft.com +developer.microsoft.com +devimages-cdn.apple.com +devstreaming-cdn.apple.com +dg-meta.video.google.com +discussionschinese.apple.com +dl.dell.com +dl.google.com +dl.l.google.com +dnl-00.geo.kaspersky.com +dnl-01.geo.kaspersky.com +dnl-02.geo.kaspersky.com +dnl-03.geo.kaspersky.com +dnl-04.geo.kaspersky.com +dnl-05.geo.kaspersky.com +dnl-06.geo.kaspersky.com +dnl-07.geo.kaspersky.com +dnl-08.geo.kaspersky.com +dnl-09.geo.kaspersky.com +dnl-10.geo.kaspersky.com +dnl-11.geo.kaspersky.com +dnl-12.geo.kaspersky.com +dnl-13.geo.kaspersky.com +dnl-14.geo.kaspersky.com +dnl-15.geo.kaspersky.com +dnl-16.geo.kaspersky.com +dnl-17.geo.kaspersky.com +dnl-18.geo.kaspersky.com +dnl-19.geo.kaspersky.com +docs.microsoft.com +doubleclick-cn.net +doubleclick.net +download.developer.apple.com +download.microsoft.com +download.mlcc.google.com +download.tensorflow.google.com +download.visualstudio.microsoft.com +downloaddispatch.itunes.apple.com +ea2cn-dev-outlet.dell.com +ea2cn-prod-outlet.dell.com +ea2cn-staging-outlet.dell.com +edge.geo.kaspersky.com +emoi-cncdn.bing.com +end-api.shallow.ink +end.shallow.ink +experiments.apple.com +f.c2r.ts.cdn.office.net +fcs.dell.com +fides-pol.apple.com +firebase-settings.crashlytics.com +fontfiles.googleapis.com +fonts.googleapis.com +fonts.gstatic.com +fs.microsoft.com +fta.dell.com +ftaapj.dell.com +ftaemea.dell.com +ftasitapj.dell.com +g0.gstatic.com +g1.gstatic.com +g2.gstatic.com +g3.gstatic.com +gbxgateway-dev.dell.com +gbxgateway.dell.com +geetest.datasink.sensorsdata.cn +gog-cdn-fastly.gog.com +gog-cdn.akamaized.net +gog.qtlglb.com +gogalaxy.gog-statics.com +gonglchuangl.net +gongyichuangyi.net +google-analytics-cn.com +google-analytics.com +googleadservices-cn.com +googleadservices.com +googleanalytics.com +googleapis-cn.com +googleapps-cn.com +googleflights-cn.net +googleoptimize-cn.com +googleoptimize.com +googlesyndication-cn.com +googlesyndication.com +googletagmanager-cn.com +googletagmanager.com +googletagservices-cn.com +googletagservices.com +googletraveladservices-cn.com +googletraveladservices.com +googlevads-cn.com +gs-loc-cn.apple.com +gs-loc.apple.com +gsp10-ssl-cn.ls.apple.com +gsp11-cn.ls.apple.com +gsp12-cn.ls.apple.com +gsp13-cn.ls.apple.com +gsp4-cn.ls.apple.com +gsp4-cn.ls.apple.com.edgekey.net.globalredir.akadns.net +gsp5-cn.ls.apple.com +gsp85-cn-ssl.ls.apple.com +gspe11-2-cn-ssl.ls.apple.com +gspe12-cn-ssl.ls.apple.com +gspe19-2-cn-ssl.ls-apple.com.akadns.net +gspe19-2-cn-ssl.ls.apple.com +gspe19-cn-ssl.ls.apple.com +gspe19-cn.ls-apple.com.akadns.net +gspe19-cn.ls.apple.com +gspe21-ssl.ls.apple.com +gspe35-ssl.ls.apple.com +gspe79-cn-ssl.ls.apple.com +gspe85-cn-ssl.ls.apple.com +gstatic-cn.com +gstaticadssl.l.google.com +gtm.oasisfeng.com +guzzoni-apple-com.v.aaplimg.com +guzzoni.apple.com +guzzoni.smoot.apple.com +hellofontpreview.oss-cn-beijing.aliyuncs.com +hellowebfonts.oss-cn-beijing.aliyuncs.com +hospital.pku.edu.cn +i.dell.com +i.pki.goog +images-cn-8.ssl-images-amazon.com +images-cn.ssl-images-amazon.com +images.apple.com.edgekey.net.globalredir.akadns.net +imasdk.googleapis.com +inappcheck-cn.itunes-apple.com.akadns.net +inappcheck-lb.itunes-apple.com.akadns.net +inappcheck.itunes.apple.com +init-kt.apple.com +init-p01md-lb.push-apple.com.akadns.net +init-p01md.apple.com +init-p01st-lb.push-apple.com.akadns.net +init-p01st.push.apple.com +init-s01st-lb.push-apple.com.akadns.net +init-s01st.push.apple.com +init.ess.apple.com +init.gc-lb.apple.com.akadns.net +init.gc.apple.com +init.itunes.apple.com +iosapps.itunes.apple.com +iosapps.itunes.g.aaplimg.com +ipcdn.apple.com +iphone-ld.apple.com +iphone-ld.origin-apple.com.akadns.net +is-ssl.mzstatic.com-cn-lb.itunes-apple.com.akadns.net +is1-ssl.mzstatic.com +is1.mzstatic.com +is2-ssl.mzstatic.com +is2.mzstatic.com +is3-ssl.mzstatic.com +is3.mzstatic.com +is4-ssl.mzstatic.com +is4.mzstatic.com +is5-ssl.mzstatic.com +is5.mzstatic.com +itunes-apple.com.akadns.net +itunes.apple.com +itunesconnect.apple.com +izattime.qcomgeo2.com +js-cdn.music.apple.com +km.support.apple.com +ksn-cinfo-geo.kaspersky-labs.com +ksn-cinfo.geoksn.kaspersky.com +ksn-crypto-info-geo.kaspersky-labs.com +ksn-crypto-kas-geo.kaspersky-labs.com +ksn-crypto-tcert-geo.kaspersky-labs.com +ksn-crypto-url-geo.kaspersky-labs.com +ksn-crypto-verdict-geo.kaspersky-labs.com +ksn-crypto-wifiplus-geo.kaspersky-labs.com +ksn-crypto-wifiplus.geoksn.kaspersky.com +ksn-dc1-cert.geoksn.kaspersky.com +ksn-dc1-file.geoksn.kaspersky.com +ksn-dc1.geoksn.kaspersky.com +ksn-file-geo.kaspersky-labs.com +ksn-file.geoksn.kaspersky.com +ksn-info-geo.kaspersky-labs.com +ksn-info.geoksn.kaspersky.com +ksn-kas.geoksn.kaspersky.com +ksn-mdr.geoksn.kaspersky.com +ksn-tcert-geo.kaspersky-labs.com +ksn-tcert.geoksn.kaspersky.com +ksn-url-geo.kaspersky-labs.com +ksn-url.geoksn.kaspersky.com +ksn-verdict-geo.kaspersky-labs.com +ksn-verdict.geoksn.kaspersky.com +learn.microsoft.com +lv.queniujq.cn +maps.apple.com +mdr.ksn.kaspersky-labs.com +menu-static.gog-statics.com +mesu-cdn.apple.com.akadns.net +mesu-china.apple.com.akadns.net +mesu.apple.com +misc-assets.itunes.apple.com +ml.cdn-apple.com +moocs.unipus.cn +music.apple.com +musicstatus.music.apple.com +mvod.itunes.apple.com +myapp.itunes.apple.com +myvs.download.prss.microsoft.com +nexus.dell.com +np-edge.itunes.apple.com +ntp.aliyun.com +ntp.tencent.com +ntp1.aliyun.com +ntp1.tencent.com +ntp2.aliyun.com +ntp2.tencent.com +ntp3.aliyun.com +ntp3.tencent.com +ntp4.aliyun.com +ntp4.tencent.com +ntp5.aliyun.com +ntp5.tencent.com +ntp6.aliyun.com +ntp7.aliyun.com +o.pki.goog +ocsp.apple.com +ocsp.pki.goog +ocsp2.apple.com +oemsoc.download.prss.microsoft.com +officecdn.microsoft.com +opencourse.pku.edu.cn +oscdn.apple.com +oscdn.origin-apple.com.akadns.net +osxapps.itunes.apple.com +osxapps.itunes.g.aaplimg.com +p.cdn.persaas.dell.com +p00.upd.kaspersky.com +p01.upd.kaspersky.com +p02.upd.kaspersky.com +p03.upd.kaspersky.com +p04.upd.kaspersky.com +p05.upd.kaspersky.com +p06.upd.kaspersky.com +p07.upd.kaspersky.com +p08.upd.kaspersky.com +p09.upd.kaspersky.com +p1-juejin.byteimg.com +p10.upd.kaspersky.com +p11.upd.kaspersky.com +p12.upd.kaspersky.com +p13.upd.kaspersky.com +p14.upd.kaspersky.com +p15.upd.kaspersky.com +p16.upd.kaspersky.com +p17.upd.kaspersky.com +p18.upd.kaspersky.com +p19.upd.kaspersky.com +p2-juejin.byteimg.com +p3-juejin.byteimg.com +p3-novel.byteimg.com +p4-juejin.byteimg.com +p5-juejin.byteimg.com +p6-juejin.byteimg.com +p6-novel.byteimg.com +p7-juejin.byteimg.com +p8-juejin.byteimg.com +p9-juejin.byteimg.com +pagead-googlehosted.l.google.com +pancake.apple.com +pba0.apple.com +pd-nk.itunes.apple.com +pd.itunes.apple.com +performanceparameters.googleapis.com +pki-goog.l.google.com +play.itunes.apple.com +play.music.apple.com +probe.siri.apple.com +prod-controlbe.floonet.goog +prod-databe.floonet.goog +prod-support.apple-support.akadns.net +prod.databe.floonet.goog +productcard.gog-statics.com +publicassets.cdn-apple.com +qiao-cn.com +qpx.googleflights.net +recaptcha-cn.net +recaptcha.net +redirector.bdn.dev +redirector.c.chat.google.com +redirector.c.mail.google.com +redirector.c.pack.google.com +redirector.c.play.google.com +redirector.c.youtubeeducation.com +redirector.gcpcdn.gvt1.com +redirector.gvt1.com +redirector.offline-maps.gvt1.com +redirector.snap.gvt1.com +redirector.xn--ngstr-lra8j.com +refresh-bkg.activation-v2.kaspersky.com +res-1.cdn.office.net +res.cdn.office.net +s.mzstatic.com +s00.upd.kaspersky.com +s01.upd.kaspersky.com +s02.upd.kaspersky.com +s03.upd.kaspersky.com +s04.upd.kaspersky.com +s05.upd.kaspersky.com +s06.upd.kaspersky.com +s07.upd.kaspersky.com +s08.upd.kaspersky.com +s09.upd.kaspersky.com +s1.mzstatic.com +s10.upd.kaspersky.com +s11.upd.kaspersky.com +s12.upd.kaspersky.com +s13.upd.kaspersky.com +s14.upd.kaspersky.com +s15.upd.kaspersky.com +s16.upd.kaspersky.com +s17.upd.kaspersky.com +s18.upd.kaspersky.com +s19.upd.kaspersky.com +s2.mzstatic.com +s3.mzstatic.com +s4.mzstatic.com +s5.mzstatic.com +safebrowsing-cache.google.com +safebrowsing.googleapis.com +scene7-cdn.dell.com +sdx.microsoft.com +se-edge.itunes.apple.com +se2.itunes.apple.com +search.itunes.apple.com +seed-sequoia.siri.apple.com +seed-swallow.siri.apple.com +seed.siri.apple.com +sequoia.apple.com +service.urchin.com +sf-api-token-service.itunes.apple.com +sh-pod2-smp-device.apple.com +shazam-insights.cdn-apple.com +shell.cdn.office.net +si.cdn.dell.com +sm.dell.com +smp-device-content.apple.com +snp.cdn.dell.com +snpi.dell.com +software.download.prss.microsoft.com +sp.itunes.apple.com +speedysub.music.apple.com +ss.bjmu.edu.cn +ssl-google-analytics.l.google.com +ssl.gstatic.com +staging-controlbe.floonet.goog +staging-databe.floonet.goog +staging.databe.floonet.goog +static-login.gog-statics.com +static.gc.apple.com +statics.teams.cdn.office.net +stocks-sparkline-lb.apple.com.akadns.net +stocks-sparkline.apple.com +store.apple.com +store.apple.com.edgekey.net +store.apple.com.edgekey.net.globalredir.akadns.net +store.storeimages.apple.com.akadns.net +store.storeimages.cdn-apple.com +storeedgefd.dsx.mp.microsoft.com +streamingaudio.itunes.apple.com +study.163.com +su.itunes.apple.com +support-china.apple-support.akadns.net +support.apple.com +supportassist.dell.com +surface.downloads.prss.microsoft.com +swallow-apple-com.v.aaplimg.com +swallow.apple.com +swcatalog-cdn.apple.com.akadns.net +swcatalog.apple.com +swcdn.apple.com +swcdn.g.aaplimg.com +swdist.apple.com +swdist.apple.com.akadns.net +swscan-cdn.apple.com.akadns.net +swscan.apple.com +sylvan.apple.com +sync.itunes.apple.com +tac.googleapis.com +tesla-cdn.thron.cn +tf-feedback.itunes.apple.com +time.amazonaws.cn +time.izatcloud.net +time.xtracloud.net +tj-pod1-smp-device.apple.com +tools.google.com +tp.louisvuitton.com +universal-activity-service.itunes.apple.com +update.crashlytics.com +update.googleapis.com +updates-http.cdn-apple.com +updates-http.cdn-apple.com.akadns.net +updates.cdn-apple.com +uplaypc-s-ubisoft.cdn.ubi.com +upp.itunes.apple.com +valid.apple.com +valid.origin-apple.com.akadns.net +vscode.download.prss.microsoft.com +vz.download.prss.microsoft.com +wear.googleapis.com +weather-data.apple.com +weather-data.apple.com.akadns.net +weather-map.apple.com +weather-map2.apple.com +weatherkit.apple.com +wscont1.apps.microsoft.com +wscont2.apps.microsoft.com +www-csb.dell.com +www-google-analytics.l.google.com +www-googletagmanager.l.google.com +www.amd.com +www.apple.com +www.apple.com.edgekey.net.globalredir.akadns.net +www.dell.com +www.destinationurl.com +www.entrust.cn +www.gstatic.com +www.microsoft.com +www.pxcc.com +www.recaptcha-cn.net +www.recaptcha.net +www.support.apple.com +www4-static.gog-statics.com +xp.apple.com +xtratime.qcomgeo2.com +xz.pphimalayanrt.com 0.zone 00.net 000.link @@ -44,6 +604,7 @@ 000868adh1.shop 000868agl1.shop 000868atz2.shop +0008bet.com 0009.xin 00091.xin 000b0fb9410281.beer @@ -84,6 +645,7 @@ 0056.com 00563.com 0058.com +005854.xin 00615.net 0066.com 00665ba1f014ee0e.com @@ -146,6 +708,7 @@ 010bianhu.com 010bjzs.com 010cb.com +010cns.com 010dell.com 010dh.com 010dsmzyy.com @@ -216,6 +779,7 @@ 01zenith.net 01zhuanche.com 01zk.com +01zph.com 020.com 020.net 0200.xin @@ -327,6 +891,7 @@ 02727.com 02766667777.com 0279.net +027965888.net 027accp.com 027aige.com 027ar51848.xin @@ -574,9 +1139,11 @@ 0539fc.com 053c.com 0543bbs.com +0543hr.com 054400.com 0545.xin 0546fdc.com +0549.xin 0550.com 055110.com 055178.com @@ -802,6 +1369,7 @@ 0757info.com 0757p.com 0757rc.com +0758net.com 0759.pw 0759home.com 0759job.com @@ -844,6 +1412,7 @@ 0775yzf.com 0776.cool 07761.com +0776hr.com 078.xin 078079.com 07890.com @@ -858,6 +1427,7 @@ 0792lsly.com 0792u.com 0793.tv +07938.com 0794zp.com 0797auto.com 0797ayzp.com @@ -896,6 +1466,7 @@ 0817ch.com 0817tv.com 0818tuan.com +0818tuangou.com 082.xin 0820.xin 0827ug.com @@ -926,10 +1497,13 @@ 08an.com 08ar.com 08b68dbbbf876114.com +08c6.com 08c8b537f1021.beer 08c9f63d31021.beer +08cms.com 08fc8f0ee3f2768a.com 08ky.com +08px.com 08qx.com 08wojia.com 08x1am20483.xin @@ -971,11 +1545,13 @@ 096.xin 0962xb24260.xin 0963d9712a525457.com +096663.com 0972xxg.com 0979.xin 0990fc0011011.beer 0991dj.com 0992.cc +099913.com 099b127ae166d8f1.com 09aag016266.xin 09b3045664731d42.com @@ -1048,6 +1624,7 @@ 0du.net 0du4br04412.xin 0duw.com +0duxs.com 0dxkge0412.xin 0e0c2e9e94622290.com 0e38e883511032.beer @@ -1085,6 +1662,7 @@ 0ggw7u16311.xin 0gklqj5hal.xin 0gmu6zwf87.xin +0gouche.com 0gps2ppnu9.xin 0gtsz9jw.xin 0gyyc924408.xin @@ -1109,6 +1687,7 @@ 0k2u4didtd.xin 0k6dm96.xin 0ka.com +0kdmutnc.xin 0kee.com 0kfq5e16254.xin 0kkkkkt.com @@ -1122,6 +1701,7 @@ 0lw1ib2sa.xin 0lzamwtu.xin 0m7eaq04292.xin +0m93wwr0.xin 0mbvyf1437.xin 0me14c18373.xin 0mfbac1gsj.xin @@ -1129,6 +1709,7 @@ 0mk9ht2449.xin 0moju716460.xin 0mrzee12192.xin +0ms.one 0ms65u0s8t.xin 0mu4f8xw0k.xin 0n469e12312.xin @@ -1447,7 +2028,6 @@ 113989.com 113dh.com 114-91.com -114.tel 1145858.com 11467.com 11478.com @@ -1600,6 +2180,7 @@ 122521.com 12260808.xyz 12272.vip +1228xbdz7m.cfd 12291.com 122bid.com 122c3e52810272.beer @@ -1742,6 +2323,7 @@ 127z.com 128456.com 1288.tv +12880.com 128d3a16610271.beer 128qd.com 128sy.com @@ -1767,6 +2349,7 @@ 130014.xyz 130158.com 131.com +13100455400.com 13112.com 13124.com 13131313131.com @@ -2029,6 +2612,7 @@ 166dns.com 16757.com 16768.com +168-hx.com 16816.com 168267xz.com 168338.com @@ -2174,6 +2758,7 @@ 175game.com 175ha.com 175hd.com +175kh.com 175pt.com 175pt.net 175sf.com @@ -2191,6 +2776,7 @@ 178.net 178198.com 178448.com +1787.ink 178768.com 178800.cc 178871.xyz @@ -2613,6 +3199,7 @@ 1checker.com 1chong.com 1cloudsp.com +1cnmedia.com 1cno.com 1d0b8b3d1800debe.com 1d1d100.com @@ -2646,7 +3233,6 @@ 1gow.net 1haigtm.com 1haitao.com -1haitun.com 1hangye.com 1haogu.com 1haosuo.com @@ -2809,6 +3395,7 @@ 2008zwe.com 20091222.com 200call.com +200rtu04.bar 200wan.com 200y.com 200zi.com @@ -2840,6 +3427,7 @@ 2025.net 202m.com 202wan.com +203328.com 2048ai.net 2048sj.com 2049baby.com @@ -2974,6 +3562,7 @@ 21shhr.com 21shipin.com 21shte.net +21sjmg.com 21sla.com 21so.com 21spv.com @@ -3021,6 +3610,7 @@ 2238202.com 2239.com 22442400.com +2247.com 224700.com 224837439.xyz 224m.com @@ -3167,6 +3757,7 @@ 24av.com 24bb0cd0c3b8480d.com 24dab50b3223e582.com +24dq.com 24geban.com 24haowan.com 24hmb.com @@ -3196,6 +3787,7 @@ 25395.vip 253952.com 253u.com +2541.com 254254.com 254game.com 2551aa29811031.beer @@ -3238,6 +3830,7 @@ 260.net 260068.com 26210.org +2629.com 263-mail.net 263.com 263.net @@ -3369,6 +3962,7 @@ 2a965464310251.beer 2agi.net 2ai2.com +2ai2.net 2amok.com 2apzhfa.xyz 2av7.com @@ -3442,6 +4036,7 @@ 2itcn.com 2j88.com 2jianli.com +2k27cny7hx.cfd 2k2k.com 2kb.com 2kfb.com @@ -3474,6 +4069,7 @@ 2qupu.com 2r3r.com 2rich.net +2sdx.com 2sey.com 2sjc.com 2sonar.com @@ -3533,6 +4129,7 @@ 301mba.com 301pk.com 3023.com +302302.xyz 302423a641021.beer 30583a8b910261.beer 3062d65271020.beer @@ -3591,6 +4188,7 @@ 315tech.com 315tsz.com 31609.com +31668.com 316watches.com 3171688.com 317608.com @@ -3653,6 +4251,7 @@ 321tips.com 321zou.com 3223.com +322799.com 322h.com 322wl.com 3230.com @@ -3767,6 +4366,7 @@ 3361.com 3366.com 3366.net +336685.com 336688.net 3366886633.com 3366img.com @@ -3783,6 +4383,7 @@ 33aml.com 33app.net 33bus.com +33dy.cc 33erwo.com 33fang.com 33ip.com @@ -3855,6 +4456,7 @@ 357.com 357global.com 358.com +359203.com 35941.com 3595.com 359798114.xyz @@ -3887,6 +4489,7 @@ 3600d.com 360114.com 360118.com +360424.com 360500.com 3608.com 360abc.com @@ -3949,6 +4552,7 @@ 360jianzhu.com 360jie.com 360jinrong.net +360jk.com 360jq.com 360jrjietiao.com 360jrkt.com @@ -4017,6 +4621,7 @@ 360worldcare.com 360wscdn.com 360wulian.net +360wyw.com 360wzb.com 360wzws.com 360xh.com @@ -4100,6 +4705,7 @@ 365fanyi.com 365gangqin.com 365gongchang.com +365good.cc 365heart.com 365hele.com 365hf.com @@ -4155,6 +4761,7 @@ 365zhuanrang.com 365zmw.com 365zsw.net +365zzx.com 3660bf60ab32baab.com 36612345.com 3663.com @@ -4266,8 +4873,10 @@ 37wanimg.com 37wanwan.com 37wanwancdn.com +37women.com 37www.com 37wxwl.com +37yzy.com 37zone.com 37zp.com 3800920.com @@ -4449,6 +5058,7 @@ 3dwhere.com 3dwwwgame.com 3dxt.com +3dxy.net 3dzyw.com 3e.net 3e089a2bd1011.beer @@ -4660,6 +5270,7 @@ 4006758160.com 4006787252.com 4006800660.com +4006806555.com 4006906600.com 4007051668.com 4007112366.com @@ -4686,6 +5297,7 @@ 4008885818.com 4009.com 4009515151.com +4009870870.com 4009991000.com 4009997658.com 400cx.com @@ -4718,6 +5330,8 @@ 409d0a7b710241.beer 40bw71.ren 40c53fc1d11021.beer +40images15.com +40images16.com 40le.com 40sishi.com 40xk.com @@ -5011,6 +5625,7 @@ 4ef06207210281.beer 4ef870db01022.beer 4everdns.com +4ewriting.com 4f0b987b410311.beer 4f411973f14e0f10.com 4f61.com @@ -5206,6 +5821,7 @@ 5170d.com 5173.com 5173cdn.com +51766.com 5177cq.com 517best.com 517cdn.com @@ -5317,6 +5933,7 @@ 51company.com 51cosmo.com 51cpm.com +51create.center 51credit.com 51csr.com 51cto.com @@ -5577,6 +6194,7 @@ 51puer.com 51qc.com 51qc.net +51qianduan.com 51qianguo.com 51qicheng.com 51qingjiao.com @@ -5669,6 +6287,7 @@ 51tjhr.com 51togic.com 51tonglu.com +51tools.info 51touch.com 51toufang.com 51tour.com @@ -5689,6 +6308,7 @@ 51u.co 51uc.com 51ukf.com +51uyi.com 51vhost.net 51vimeo.com 51vip.biz @@ -5953,6 +6573,7 @@ 52gaoxiao.com 52gg.com 52ggd.com +52gongju.net 52grz.com 52guzhuang.com 52gvim.com @@ -6120,6 +6741,7 @@ 531pool.com 532106.com 53233.org +532588.com 533.com 53326.com 5334.com @@ -6136,6 +6758,10 @@ 5368111.com 537300.com 53797.vip +537images41.com +537images42.com +537images45.com +537images47.com 5385s.com 538618.com 538a9b8b890bff34.com @@ -6566,6 +7192,7 @@ 595.bet 595.cc 59519.com +5956777.com 595818.com 5959.vip 5959068.com @@ -6639,6 +7266,7 @@ 5baike.com 5ber.com 5bite.com +5biying.com 5c407acb510301.beer 5c554118a1021.beer 5cb69e8045837fa9.com @@ -7016,6 +7644,7 @@ 637600.com 6380.org 6383.com +638300.com 6385844.com 63860.vip 639311.com @@ -7112,8 +7741,10 @@ 666532.xyz 666546.xyz 666548.xyz +66661s.com 6666343ac4.shop 6666519.net +66665s.com 66666.host 666666.host 6666m.vip @@ -7560,7 +8191,6 @@ 71k.com 71lady.com 71p.net -71shi.net 71tao.com 71tech.com 71txt.com @@ -7615,6 +8245,7 @@ 72e.net 72en.com 72g.com +72gu.com 72home.com 72kjmb.com 72la.com @@ -7786,7 +8417,6 @@ 777haoka.com 777lala.com 777moban.com -777ppt.com 777sy.com 777yh.am 7788.com @@ -7827,6 +8457,7 @@ 77shu.com 77shuku.la 77shuku.org +77shw.com 77socks.com 77tianqi.com 77vcd.com @@ -7845,6 +8476,7 @@ 785836.com 785956.com 78654321.com +78665.vip 786e7138b09934b3.com 78785z.com 7881.com @@ -8083,6 +8715,7 @@ 800cdn.com 800du.com 800hr.com +800jcw.com 800li.net 800lie.com 800lj.com @@ -8165,6 +8798,7 @@ 8130088.com 81312.com 81366j.com +815.pub 8158.com 81593.com 81629.com @@ -8259,6 +8893,9 @@ 834834009.com 834834vip4.com 835444.cc +835images61.com +835images73.com +835images76.com 835images83.com 83661111.net 83666.com @@ -8282,6 +8919,7 @@ 84232.com 84308.com 84399.com +844wan.com 84560262.xyz 845de4aca1019.beer 845e4a87610292.beer @@ -8344,6 +8982,7 @@ 86754.org 867867jh.com 8682.cc +8684.com 8686c.com 8688g.com 86933.com @@ -8446,6 +9085,7 @@ 88116008.com 8811777.com 881794d571016.beer +881812c.com 88183322.com 88183355.com 881ac9cfd560166d.com @@ -8545,6 +9185,7 @@ 88ttv.com 88wbet.me 88xiaoshuo.com +88xr.org 88y5.com 88ysg.com 88zha.com @@ -8655,7 +9296,6 @@ 8liuxing.com 8llp.com 8lun.com -8m2.net 8m8t.com 8mcn.com 8mhh.com @@ -8943,6 +9583,7 @@ 91tvb.cc 91tw.net 91up.com +91vpn.com 91vps.com 91vrchat.com 91vst.com @@ -9012,6 +9653,7 @@ 927game.com 928.sale 928vbi.com +928xpj.com 92913.com 929825.com 92987.com @@ -9438,6 +10080,7 @@ 98yl.com 98zhibo.com 98znz.com +98zw.com 99.com 99113guoji.com 99114.com @@ -9504,6 +10147,7 @@ 9998.tv 99988866.xyz 9999224xtz1.shop +99999dns.com 999ask.com 999brain.com 999d.com @@ -9616,6 +10260,7 @@ 9c941a3b41021.beer 9ca4b8eca1019.beer 9cao9.com +9cb.com 9ccapital.com 9ce69ea6b10272.beer 9cfa10ad910291.beer @@ -9704,6 +10349,7 @@ 9laidu.net 9linux.com 9liuda.com +9ljr84.xyz 9longe.net 9man.com 9mayi.com @@ -9775,7 +10421,6 @@ 9yuntu.com 9yuonline.com 9yz.com -9zadx.com 9zas5.com 9zhen.com 9znet.com @@ -9839,6 +10484,7 @@ a5lt.com a5xiazai.com a5y.net a61cee76f4df41b1.com +a632079.me a6562752811032.beer a6h8.com a6ksw.net @@ -9878,10 +10524,13 @@ aa360.net aa433d1b51013.beer aa43z7.com aa50b44761011.beer +aa56238652.com aa5c82aeb10271.beer aa65535.com aa6666.com +aa67678156.com aa6f0397a3cf50d7.com +aa76858896.com aa77kk.com aa84c23fc4aa335a.com aaalawfirm.com @@ -9925,6 +10574,7 @@ aaxinwen.net aaygw.com aayu.today aayun.icu +aazao.com ab-sm.com ab126.com ab365.com @@ -10069,6 +10719,7 @@ acclb.com acconsys.com accopower.com account.htcvive.com +accountboy.com accr.cc accsh.org acctdns.net @@ -10242,6 +10893,7 @@ addpv.com addww.com addx.live ade8.com +adeasyx.com adebang.com adebibi.com adeccogroupcn.com @@ -10296,7 +10948,6 @@ admin5.com admin5.net admin6.com admin88.com -admincdn.com adminer.com admintony.com adminxe.com @@ -10326,7 +10977,6 @@ adplusx.com adpsh.com adqtt.net adquan.com -adrkwai.com ads66.com ads8.com adsalecdn.com @@ -10397,7 +11047,6 @@ adxmax.com adxmq.com adxpand.com adxqd.com -adxsenmeng.com adxvip.com adxwork.com adxyun.com @@ -10480,6 +11129,7 @@ afbd4a3905a4a9a5.com afca-asia.org afca6b82c11032.beer afcec.com +afcfsf.xyz afdian.com afdian.net afdiancdn.com @@ -10506,6 +11156,7 @@ afshanghai.org afszc.com aft1v1.com aftersale-amazon.com +aftersales.cc aftvc.com afunapp.com afuvip.com @@ -10552,6 +11203,7 @@ agrosg.com agrowingchina.com agstt.com agtech.ltd +agucn.com agui.cc agumd.com agv-amr.com @@ -10735,6 +11387,7 @@ ahtongyuan.com ahtrq.com ahtxyyjt.com ahtypg.com +ahuano.com ahubbs.com ahudows.com ahuyi.com @@ -10855,7 +11508,7 @@ aichangbaishan.com aichaoxing.com aichat.net aichat1234.com -aichaxun.com +aichehome.com aichi-zhe.com aichinaw.com aichunjing.com @@ -11077,6 +11730,7 @@ aimergroup.com aimeter.com aimgroupcn.com aimicron.com +aimilebc.com aimin1979.com aimingmed.com aimingtai.com @@ -11284,6 +11938,7 @@ aivote.com aiwaly.com aiwan4399.com aiwan91.com +aiwanba.net aiwanma99.com aiwebsec.com aiwei365.net @@ -11475,6 +12130,7 @@ alavr.com alayanew.com alayu.com alayunchina.com +alazv.com alc-iot.com alcasset.com alcha.com @@ -11538,6 +12194,7 @@ alibabafoundation.com alibabafuturehotel.com alibabagroup.com alibabaonline.com +alibabapictures.com alibabaplanet.com alibabastars.com alibabatech.org @@ -11568,8 +12225,6 @@ alidayu.com aliddmall.com alidns.com alidns.net -alidnsv4.com -alidnsv5.com aliedge.com aliensidea.com alientek.com @@ -11596,6 +12251,7 @@ alijijinhui.org alijk.com alikunlun.com alikunlun.net +alili.tech aliloan.com aliluya.com alimama.com @@ -11651,6 +12307,7 @@ alitrip.hk alittle-tea.com aliued.com aliunicorn.com +aliuv.com alivecdn.com alivv.com aliway.com @@ -12163,12 +12820,12 @@ animate.art animationcritics.com animetamashi.com animetaste.net +anischools.com anitama.net aniu.com aniu.so aniu.tv aniworld.tv -anixplayer.net anjgas.com anji-ceva.com anji-logistics.com @@ -12253,7 +12910,6 @@ anqingonline.com anqingzhiyun.com anqn.com anqu.com -anquan anquan.info anquan.org anquanbao.com @@ -12361,6 +13017,7 @@ anxiangkeji.net anxiaoer.com anxin.com anxin.link +anxin168.com anxin360.com anxin360.net anxinapk.com @@ -12645,6 +13302,7 @@ apkdv.com apkevery.com apkhot.com apkj.com +apkpackagesys.com apkplug.com apkrj.com apkxz.com @@ -12675,7 +13333,6 @@ apowo.com apowogame.com apoyl.com app-router.com -app-static.com app001.com app0772.com app111.com @@ -12826,6 +13483,7 @@ aqsiqauto.com aqtd.com aqtowngas.com aqtwpc.com +aqtwwx.net aqtwwxemail.com aqtxt.net aqua-worth.com @@ -13097,7 +13755,6 @@ asifadeaway.com asilu.com asim-emc.com asimi8.com -asinking.com asit.cc asjnu.com ask.amd.com @@ -13188,7 +13845,6 @@ at188.com at317.com at58.com at78.com -at8.fun at98.com ata-edu.com ata-test.net @@ -13435,6 +14091,7 @@ avanzacorp.com avaryholding.com avatamveda.com avatarmind.com +avatarmobi.com avatr.com avaya.hk avc-mr.com @@ -13445,6 +14102,7 @@ aves.art avfline.com avgh5.com avgnati.com +avgnatii.com avgroft.com aviationsnip.com avic-acs.com @@ -13627,6 +14285,7 @@ awsdns-cn-63.biz awsdns-cn-63.com awsdns-cn-63.net awsdns-vip.com +awsok.com awsonamazon.com awspaas.com awspony.com @@ -13715,6 +14374,7 @@ ayrmt.com ayrq.com aysbxxh.com ayshq.com +aysjhod.xyz aysjyjjh.com ayump.com ayunlian.com @@ -13761,7 +14421,6 @@ azt365.com azuiblc.com azure-wave.com azure.cc -azureflame.cloud azureflying.com azuremigrate.download.prss.microsoft.com azuremigratetest.download.prss.microsoft.com @@ -13783,7 +14442,6 @@ b-fairy.com b-k-automation.com b-raymedia.com b.biz -b.c2r.ts.cdn.office.net b01.net b02f0d7f3745ba7c.com b041e427810281.beer @@ -13795,6 +14453,7 @@ b18e55f8a1010.beer b19a352d2336941a.com b1bj.com b1n.net +b1qg.com b214bd8861009.beer b2216419b1013.beer b22fb6d6d1015.beer @@ -14268,6 +14927,7 @@ baiyang.com baiyangseo.com baiycap.net baiye5.com +baiyewang.com baiyi.shop baiyikc.com baiying.com @@ -14321,6 +14981,7 @@ balihe.com balijieji.com balimtoy.com balingtxt.com +balldollars.com ballgametime.com ballpure.com balltv.cc @@ -14499,9 +15160,7 @@ bao-fang.com bao-hulu.com bao12333.com bao369.com -baoanbi.com baoanchina.com -baoangold.com baob123.com baobanli.com baobao001.com @@ -14630,6 +15289,7 @@ baowugroup.com baoxian.com baoxian72.com baoxianjie.net +baoxianqi.com baoxianshichang.com baoxiaobar.com baoxiaohe.com @@ -14767,7 +15427,6 @@ bb39977653.com bb3a14ca57af4d81.com bb52e27c75560d3f.com bb730b04b6cc8c57.com -bb888xx999.com bbanp.com bbaod.com bbaqw.com @@ -14863,6 +15522,7 @@ bbxstjx.com bbzhh.com bbzhi.com bbzhsw.com +bbzxky.fun bc150.com bc4785bd211012.beer bc5539.com @@ -14907,6 +15567,7 @@ bchrt.com bcisz.org bcitb.com bcjkcloud.com +bcjp.net bclcraft.com bcmcdn.com bcmonomial.xyz @@ -14954,7 +15615,6 @@ bdatu.com bdbae.com bdc-rays.com bdcb.cc -bdcgslb.com bdcgz.com bdchangtujs.com bdchina.com @@ -15146,7 +15806,6 @@ beidouone.com beidousafety.org beidouxingxueche.com beiduolab.com -beie.org beierbio.com beierjin.net beieryouxi.com @@ -15473,6 +16132,7 @@ bestweshop.com bestwesternfelicityshenzhen.com bestzone.org bet007.com +bet2268.com bet2396.com bet33040.com bet3739.com @@ -15652,6 +16312,7 @@ bhgroups.com bhgsb.com bhhfsc.com bhhospital.com +bhhsx.com bhhthl.com bhidi.com bhiv.pro @@ -16360,11 +17021,11 @@ bjanjili.com bjartmuseum.com bjatv.com bjbaidianfeng.com -bjbaifan.com bjbalb.com bjbaodao.net bjbb.com bjbeichen.com +bjbeifangjx.com bjbkwy.com bjblackhole.com bjbna.com @@ -16512,7 +17173,6 @@ bjhwtx.com bjhwxy.com bjhx110.com bjhxqh.com -bjhxwl.xyz bjhxzlyy.com bjhytjxjg.com bjhzzs.com @@ -16532,6 +17192,7 @@ bjjbls.com bjjchf.com bjjdwx.com bjjfsd.com +bjjhhft.com bjjhhn.com bjjhsw.com bjjihui.com @@ -16820,6 +17481,7 @@ bjywt.com bjyxl.com bjyyb.net bjyyun.com +bjzalaw.com bjzaxy.com bjzbb.com bjzbkj.com @@ -17185,6 +17847,7 @@ bobdog.com boblog.com bobo.com bobo2008.com +bobo91.com bobobird.com bobohome.store bobopic.com @@ -17437,6 +18100,7 @@ bosdsoft.com boseclub.com boseip.com bosen-fuji.com +bosenrui.com bosera.com bosera.com.hk boshgame.com @@ -17467,6 +18131,7 @@ boss-young.com bosscdn.com bossco.cc bossgoo.com +bossgy.com bosshr.com bossincom.com bosszhipin.com @@ -17522,6 +18187,7 @@ boydwang.com boyi.co boyikang.com boyingsj.com +boyinhotel.com boyishu.me boylondonchina.com boyoucy.com @@ -17562,6 +18228,7 @@ bqbh.org bqfy.com bqg8.cc bqg99.cc +bqgjt.work bqgwap.com bqgxsw.com bqjeep212.com @@ -18044,6 +18711,7 @@ bxldz.com bxltw.com bxmd51.com bxnfsy.com +bxnjmj.com bxpedia.com bxr.im bxrfund.com @@ -18051,6 +18719,7 @@ bxsnews.com bxtag.com bxwatch.com bxwljt.com +bxwst.com bxwx.cc bxwx.io bxwx.tv @@ -18288,6 +18957,8 @@ bzcjw.com bzcm.net bzcm88.com bzcw8.com +bzd6688.com +bzddrive.com bzfar.com bzfpms.com bzfwy.com @@ -18357,6 +19028,7 @@ c-yl.com c03b92b501009.beer c098d113b11031.beer c0c0b511e1009.beer +c114.net c122e63a111031.beer c133.com c1a8330703dc6f12.com @@ -18789,7 +19461,6 @@ cangdu.org cangfengzhe.com canghaiip.com canghaimachine.com -cangjggame.com canglanghospital.com cangmang.xyz cangoonline.com @@ -18828,7 +19499,6 @@ cantonde.com cantonfairad.com cantontower.com canvcan.com -canviewone.com canway.net canwayit.com canwaysoft.com @@ -18867,6 +19537,7 @@ caohai.com caohaifeng.com caohejing.com caohua.com +caoke.net caoliao.net caomall.net caomei.wiki @@ -19006,6 +19677,7 @@ casibase.com casic-addsino.com casic-t.com casic.com +casic304.com casic3s.com casicloud.com casicyber.com @@ -19110,6 +19782,7 @@ cbiachina.com cbiao.com cbiec.com cbiec.net +cbigame.com cbinews.com cbismb.com cbitft.com @@ -19392,6 +20065,7 @@ ccpitecc.com ccpitfujian.org ccpitgs.com ccpitgx.org +ccpithebei.com ccpithn.org ccpitjinan.org ccpitjs.org @@ -19506,10 +20180,13 @@ ccwqtv.com ccwto.net ccxcn.com ccxcredit.com +ccxhfk.com ccybeta.cc +ccygmy.com ccyia.com ccynice.com ccyts.com +ccyunmai.com ccyyxx.com cczel.com cczjyy.com @@ -19922,6 +20599,7 @@ cdxsxbx.com cdxw.net cdxwcx.com cdxwxy.com +cdxydb.com cdydlx.com cdyee.com cdyestar.com @@ -20080,6 +20758,7 @@ centainfo.com centaland.com centanet.com centauriglobal.com +centbrowser.net centec.com cententcymbals.com centerm.com @@ -20400,10 +21079,10 @@ chachaba.com chachawenshu.com chache-cn.com chache808.com +chacheku.com chachengji.com chacihai.com chacuo.net -chadaodao.com chadianhua.net chadoc.com chaej.com @@ -20450,6 +21129,7 @@ chakahui.com chakonghao.com chakuaizhao.com chalaili.com +chalaoshi.de chalcochem.com chalieco.com challenge-21c.com @@ -20578,6 +21258,7 @@ chanjue.net chanluntan.com chanmama.com channelbeyond.com +channingsun.bid chanpay.com chanpin100.com chanpindashi.com @@ -20590,6 +21271,7 @@ chanxuehezuo.com chanzhi.org chao-cn.com chao-fan.com +chaoart.com chaobohui.com chaodamould.com chaodavalves.com @@ -20601,6 +21283,7 @@ chaofantian.com chaoge.press chaogejiaoyu.com chaogu1688.com +chaohuatech.com chaohuishou.com chaoji.com chaojibiaodan.com @@ -20667,7 +21350,6 @@ charmsunfund.com chartboost-china.com chase-secure55.wiki chasedorisu.com -chasedream.com chaseyanyu.net chashebao.com chashudi.com @@ -20850,6 +21532,7 @@ chenduaomei.com chener.net chenfan.info cheng-sen.com +chengaizixun.com chengan-web.com chengan.tech chengbanggroup.com @@ -21017,6 +21700,7 @@ chesthospital.com chesudi.com chetiansi.com chetodeng.com +chetuanwang.net chetuobang.com chetx.com chetxia.com @@ -21087,6 +21771,7 @@ chiefpharma.com chieftin.org chietom.com chihao.com +chihe.so chihealbio.com chiheba.com chiheshan.com @@ -21605,7 +22290,6 @@ chinaeinet.com chinaeinv.com chinaejiao.com chinaembassy.at -chinaemd.com chinaepoxy.com chinaepu.com chinaequity.net @@ -22068,6 +22752,7 @@ chinasufa.com chinasunglow.com chinasuntree.com chinasupercloud.com +chinasuperwool.com chinaswa.org chinaswim.com chinaswimmer.com @@ -22178,7 +22863,6 @@ chinaweiyu.com chinaweizheng.com chinawepon.com chinawerewolf.com -chinawestagr.com chinawie.com chinawindnews.com chinawinjoy.com @@ -22187,7 +22871,6 @@ chinawitmedia.com chinawr.net chinaws.net chinawssdxh.com -chinawu.com chinawudang.com chinawulong.com chinawutong.com @@ -22202,6 +22885,8 @@ chinaxinhuaair.com chinaxintu.com chinaxinzhi.com chinaxiuqi.com +chinaxiv.com +chinaxiv.net chinaxiv.org chinaxpp.com chinaxq.com @@ -22380,6 +23065,7 @@ chn-cstc.com chn-das.com chn0769.com chn112.com +chn168.com chnart.com chnau99999.com chnboxing.com @@ -22826,7 +23512,6 @@ cikelink.com cilgroup.org cilin.org cilishenqi.cc -cilisouou.com cilogo.com cim2025.net cimamotor.com @@ -22985,6 +23670,7 @@ ciyoga.org ciyuan.men ciyuanji.com ciyuans.com +ciyun.link ciyynodegroup.cyou cizip.com cj0515.com @@ -23469,7 +24155,6 @@ cm-health.com cm-inv.com cm-iov.com cm-worklink.com -cm1.hk cm233.com cm3721.com cm442.com @@ -23645,6 +24330,7 @@ cn-cr.com cn-elite.com cn-em.com cn-ferment.com +cn-food.net cn-ghtube.com cn-goldeneagle.com cn-hbs.com @@ -24228,6 +24914,8 @@ cnmsn.net cnmstl.net cnmtpt.com cnmuseum.com +cnmysoft.com +cnn-wali.com cnn100.com cnnb.com cnnbfdc.com @@ -24380,6 +25068,7 @@ cnshuizu.com cnshzm.com cnside.com cnsihai.com +cnsikao.com cnsimeng.com cnsimg.net cnsimin.com @@ -24454,6 +25143,7 @@ cntv.com cntvan.com cntwg.com cntyjt.com +cntywhcm.com cnu.cc cnuninet.net cnuozu.com @@ -24502,6 +25192,7 @@ cnxiangyan.com cnxiantao.com cnxianzai.com cnxiaobai.com +cnxiaoyuan.com cnxibu.com cnxieku.com cnxile.com @@ -25004,7 +25695,6 @@ copywatchstyle.com cor-games.com coralset.com core-biopharma.com -core-flow.org corebai.com coreesports.net corehalo.com @@ -25042,6 +25732,7 @@ coship.com cosideabl.com cosinedu.com cosineg.com +cosize.com cosjiang.com cosmileonly.com cosmo-lady.com @@ -25083,6 +25774,7 @@ cowcs.com cowealth.com cowellhealth.com cowlevel.net +cowrycare.com cowtransfer.com coyigroup.com coyis.com @@ -25506,6 +26198,7 @@ cqkytq.com cqlaifu.com cqlandtower.com cqlba.com +cqlbjg.com cqld.com cqlfn.com cqlhyy.com @@ -25745,6 +26438,7 @@ cqxdfpr.com cqxdjgxx.com cqxdnzyy.com cqxdyy.com +cqxfxh.com cqxh120.com cqxhdc.com cqxhyz.com @@ -25762,6 +26456,7 @@ cqxqz.com cqxshs.com cqxsxt.com cqxszx.net +cqxtcx.com cqxwdb.com cqxwtx.com cqxxly.net @@ -25864,6 +26559,7 @@ cr20g.com cr6868.com cr8gc.com crabchina.com +cracmedu.org craftcontact.com craftria.com craim.net @@ -26035,6 +26731,7 @@ crx4.com crxdl.com crxsoso.com cry33.com +crysound.com crystaledu.com crystalskeleton.com cryuantafund.com @@ -26131,7 +26828,6 @@ csghy.com csgjhzzx.com csgjjt.com csgkjt.com -csgky.net csgocn.net csgongshui.com csgpc.org @@ -26696,6 +27392,7 @@ cxbiji.com cxbio.com cxbsx.com cxbz958.com +cxc233.com cxcc.me cxcentury.com cxcijiedu.com @@ -26749,7 +27446,6 @@ cxtrip.cc cxumol.com cxw.com cxwl.com -cxwlad.com cxxjs.com cxy521.com cxy61.com @@ -26889,7 +27585,6 @@ cytstibet.com cytuig.com cyuandao.com cyudun.net -cyuegame.com cyuew.com cyuntech.com cyvalve.com @@ -26983,6 +27678,7 @@ czjoel.com czjpw.com czjsy.com czjyzx.net +czkbdq.net czkctl.com czkingdee.com czlgj.com @@ -27030,6 +27726,7 @@ cztvcdn.com cztvcloud.com cztzkg.com czur.com +czvv.net czwie.com czws.com czwxbyq.com @@ -27092,7 +27789,6 @@ d1f25004011021.beer d1f41d7b74d2717d.com d1ld.com d1lx.com -d1mgroup.com d1miao.com d1mm.com d1net.com @@ -27204,6 +27900,7 @@ dabjy.com daboluo.net dabusi.com dacai.com +dacang.ltd dacankao.com dacankao.net dacaomei.com @@ -27334,6 +28031,7 @@ daicuo.cc daidaicp.com daidaipu.com daidongxi.com +daigege.com daigou.com daihing.com daijuchuang.net @@ -27393,6 +28091,7 @@ dajianhui.com dajianyouju.com dajiashequ.com dajiashuo.com +dajiazhao.com dajiazhongyi.com dajiazulin.com dajibacdn.com @@ -27424,6 +28123,7 @@ dakejie.com dakekj.com dakele.com dakw.xyz +dalaba.com dalanyouxi.com dalao.net dalaosz.com @@ -27506,6 +28206,7 @@ dandang.org dandanhou.net dandanjiang.tv dandanman.com +dandanplay.com dandantang.com dandanvoice.com dandanz.com @@ -27894,6 +28595,7 @@ dayima.com dayin.com dayin.la dayin8.com +dayinhu.com dayinjiqudong.com dayinmao.com dayinpai.com @@ -27916,6 +28618,7 @@ dayu-valve.com dayu.com dayu.work dayuanqy.com +dayuansouti.com dayuanzong.com dayucdntip.com dayue.com @@ -28041,6 +28744,7 @@ dcef3671664fa11b.com dcement.com dcexport.work dcg.microsoft.com +dcg123.com dcgqt.com dcgsi.com dcgstd.com @@ -28149,6 +28853,7 @@ ddjs-cn.com ddjsyx.com ddkanqiu.cc ddkanqiu.net +ddkanqu.com ddkids.com ddkt365.com ddky.com @@ -28398,6 +29103,7 @@ dellhpibm.com dellrsm.com dellzj.com delong-group.com +delonggou.com delonix.group delovabio.com delphi-connect.com @@ -28520,6 +29226,7 @@ dev59.com devashen.com devask.net devbean.net +devblogs.microsoft.com devclub.cc devedu.net develenv.com @@ -28685,6 +29392,7 @@ dg-niuniu.com dg-tcm.com dg11185.com dg121.com +dg456.com dg688.com dgaefi.org dgaiia.com @@ -29266,6 +29974,7 @@ dinzd.com diodecy.com dioenglish.com diomasce.com +dionly.com diopic.net dious-f.com dipan.com @@ -29308,6 +30017,7 @@ ditan.com ditan360.com ditian-tech.com ditianshanhe.com +ditiefuli.com ditiezu.com ditiezu.net ditrit.com @@ -29636,6 +30346,7 @@ dn.com dn23.com dn8188.com dnake-park.com +dnatupu.com dnbbn.com dnbbs.com dnbiz.com @@ -30121,6 +30832,7 @@ dopoil.com dopool.com dora-control.cdnetworks.com dora-family.com +dorapp.com dorcen.com doremi.ink dorgean.com @@ -30147,7 +30859,6 @@ doteck.com dotgate.com dothantech.com doticloud.com -dotreamexpo.com dotty-china.com dotwe.org dou.bz @@ -30192,6 +30903,7 @@ doudoubird.com doudouditu.com doudouguo.com doudouknot.com +doudouso.com doufan.tv doufm.net dougong.net @@ -30263,7 +30975,6 @@ douyongtuan.com douyou100.com douyu.com douyu.tv -douyuedui.com douyuex.com douyuscdn.com douyutv.com @@ -30689,6 +31400,7 @@ dudujuzi.com dudulu.org dudutalk.com duelcn.com +dugrqnn.xyz duguying.net duhao.net duhaobao.net @@ -30702,7 +31414,6 @@ duibimao.com duiduihuishou.com duiduilian.com duiduipengds.com -duifene.com duijie666.com duikuang.com duimg.com @@ -30739,7 +31450,6 @@ dumasoftware.com dumi0898.com dumpapp.com dun.la -dun222.com dun555.com dun599.com dunan.net @@ -30851,6 +31561,7 @@ duopingka.com duoqu.com duorenwei.com duorou.com +duorou42.com duosai.com duose.com duosenfashion.com @@ -31014,7 +31725,6 @@ dxlfile.com dxm-cdn.com dxm-int.com dxm-yibai.com -dxmbaoxian.com dxmjinr.com dxmjuhe.com dxmlcf.com @@ -31726,6 +32436,7 @@ ec3s.com ec45f2d84111f960.com ec4ca59341009.beer ec4e54545d0e31fa.com +ec51.com ec6056a95386f752.com ec8j.com ecac1cd921021.beer @@ -31742,6 +32453,7 @@ ecarxgroup.com ecb43b6f5064acc6.com ecboo.com ecbos.com +ecbuy.net eccang.com eccc-china.com eccdnx.com @@ -32064,6 +32776,7 @@ eee-eee.com eee4.com eeeam.com eeedri.com +eeedu.net eeeen.com eeeetop.com eeeff.com @@ -32130,7 +32843,6 @@ effapp.com efficient.hk effirst.com efglobal-gy.com -efindtravel.com efivestar.com efl-tech.com eflagcomm.com @@ -32334,6 +33046,7 @@ ekumb.com ekuzy.com ekweixin.com ekwing.com +ekxx.net elabinfo.com elabpic.com elaina.vin @@ -32364,6 +33077,7 @@ eleduck.com elefang.com elefirst.com elegant-prosper.com +elegsant.com elelx.com eleme.hk eleme.io @@ -32477,6 +33191,7 @@ embryform.com embryochina.com embsky.com emby.men +emby.yun embyvip.org emcbj.com emcdir.com @@ -32741,6 +33456,7 @@ epaynfc.com epbiao.com epchina.com epcnn.com +epcservicio.com epcsw.com epd3.com epday.com @@ -32762,6 +33478,7 @@ epichust.com epinautomation.com epinduo.com epinga.com +epinhoo.com epinv.com epinzu.com epjike.com @@ -32872,6 +33589,7 @@ erongdu.com erp198.com erp321.com erp321.net +erpang.cc erpingge.com erpwd.com erro.life @@ -32941,6 +33659,7 @@ esjlf.com esjson.com esk365.com eskedu.com +eskimall.com eskysky.com eslosity.com eslygroup.com @@ -33022,6 +33741,9 @@ etccenters.com etcchebao.com etcgsx.com etchcb.com +etcifk.vip +etcifw.vip +etciwe.vip etcsd.com etcsx.com etczs.net @@ -33034,6 +33756,7 @@ etest8.com eteste.com etf.group etf88.com +etfcjz.com etfiber.net etg56.com ethainan.com @@ -33152,6 +33875,7 @@ ev123.com ev123.net ev3ds.com ev88.com +ev98.com eva-group.com eva001.com evacellbio.com @@ -33523,7 +34247,6 @@ f-dou.com f-insight.com f-stack.org f.biz -f.c2r.ts.cdn.office.net f.cx f008.com f015dfc4510292.beer @@ -33662,6 +34385,7 @@ fafawang.com fafeng.com fafuli.com fagaoshi.com +fageka.com fagua.net faguowenhua.com fahai.com @@ -33799,6 +34523,7 @@ fango8.com fangpaiwang.com fangqk.com fangrun.com +fangshan.us fangshanzi.com fangsheji.com fangshengaxle.com @@ -34187,7 +34912,6 @@ fd7c.com fdbatt.com fdc0746.com fdcjj.org -fdctax.com fdcyun.com fdczbstatic.com fdd0cd79c10281.beer @@ -34251,7 +34975,6 @@ feelec.net feelnowtrip.com feelsoar.com feemoo.com -feemoo.hk feemoo.vip feepan.com feeprint.com @@ -34300,7 +35023,6 @@ feierlaiedu.com feiersmart.com feifandesign.com feifanindustry.com -feifanmi.com feifantxt.com feifantxt.net feifdx.com @@ -34323,6 +35045,7 @@ feihua.com feihuadns.com feihuo.com feihutaoke.com +feijie.org feijing88.com feijipan.com feijiu.net @@ -34402,6 +35125,7 @@ feixiansoft.com feixiaodata.com feixiaoqiu.com feixiong.tv +feixue666.com feixuege.com feixueteam.net feiyang.com @@ -34425,6 +35149,7 @@ feizan.com feizhaojun.com feizhiyi.com feizhu.com +feizhuke.com feizhupan.com feizhuqwq.com feizutrip.com @@ -34518,7 +35243,6 @@ fengli.com fengli.su fengliankeji.com fenglichem.com -fenglingkj.com fenglingroup.com fenglinjiu.com fenglinlab.com @@ -34677,6 +35401,7 @@ ffy.com ffyoo.com ffzww.com fg.cc +fg114.com fgba.net fgcndigital.com fgeekcloud.com @@ -34710,6 +35435,7 @@ fhfxys.com fhgame.com fhgjds.com fhguotai.com +fhl05161ps.com fhldns.com fhldns.net fhlmotor.com @@ -34917,6 +35643,7 @@ fixdown.com fixhdd.org fixmath.com fiyta.com +fj-atfz.com fj-ba.com fj-cc.com fj-eprint.com @@ -34950,6 +35677,7 @@ fjcyl.org fjdaily.com fjdaze.com fjdc.xyz +fjdf.vip fjdfxy.com fjdh.com fjdkjt.com @@ -35021,6 +35749,7 @@ fjndwb.com fjnet.com fjnhjt.com fjpca.com +fjpcz.com fjpicc.com fjpit.com fjpta.com @@ -35357,6 +36086,7 @@ folidaymall.com folij9nry5cxy8.com followintg.com folou.com +fondcosplay.com foneplatform.com fonian.com fonsview.com @@ -35438,6 +36168,7 @@ forkliftnet.com forlinx.com form-create.com formeasy.cc +formeneed.com formingcn.com formssi.com formtalk.net @@ -35616,6 +36347,7 @@ freedomscm.com freedoonline.com freegeeker.com freehpcg.com +freeidea.win freejishu.com freekaobo.com freekaoyan.com @@ -35883,7 +36615,6 @@ ftfx.ink fthformal.com fthgz.com fthimalayandb.com -ftihzjg.com ftium4.com ftlcloud.com ftls.xyz @@ -36584,7 +37315,6 @@ gajsk.com gal3d.info gala-v.com galaxix.com -galaxy-adx.com galaxy-geely.com galaxy-immi.com galaxyappstore.com @@ -37173,6 +37903,7 @@ gdfanhua.com gdfcjz.com gdfeiyang.com gdfenxiao.com +gdfggvfsd.asia gdfgq.club gdfjsh.org gdfplaza.com @@ -37354,7 +38085,6 @@ gdronggang.com gdroro.com gdrqj.org gdrsrc.com -gdrtt.net gdruien.com gdruisheng.com gdrxyy.com @@ -37605,6 +38335,7 @@ geeknev.com geekpark.net geekpwn.com geekpwn.org +geeksblog.cc geeksman.com geektcp.com geektutu.com @@ -37656,9 +38387,11 @@ geihui.com geilicdn.com geilijiasu.com geilijiasu.net +geiliwx.com geimian.com geindex.com geiniwan.com +geisnic.com geizan.cc gelaha.com gelicang.net @@ -38022,6 +38755,7 @@ ggxdsmzx.com ggxhx.com ggxiaolinmy.com ggxinzhitaimy.com +ggxjhk.com ggxsjz.com ggxue.com ggxx.net @@ -38445,6 +39179,7 @@ glmingke.com glmmodels.com glncn.com glnewstar.com +glo-slotsimulator.com global-dba.com global-download.acer.com global-harbor.com @@ -38644,6 +39379,7 @@ gmt-cn.com gmtv.cc gmtzy.com gmugmu.com +gmw.com gmwiki.com gmxmym.ren gmya.net @@ -38782,6 +39518,7 @@ gojira.net gokaigai.com goke.com gokemicro.com +gokols.com gokuai.com golahsg.com golang8.com @@ -38838,7 +39575,6 @@ goldstonepack.com goldsunchn.com goldsunmachinery.com goldsupplier.com -goldtianyan.com goldvole.com goldwaterhk.com goldwind.com @@ -39413,6 +40149,7 @@ gseen.com gsensebot.com gsfilter.net gsflcp.com +gsfrj.com gsfybjy.com gsfycloud.com gsfzb.com @@ -39827,10 +40564,12 @@ guanziheng.com guapaijia.com guardease.com guardrailchina.com +guatedu.com guazhuan.com guazi-apps.com guazi.com guazi.net +guazi75637736.cc guazipai.com guazistatic.com guazixs.com @@ -39858,7 +40597,6 @@ gugong.net gugu.fun gugud.com gugudang.com -gugudata.com guguread.com gugushuzi.com guguyu.com @@ -40046,7 +40784,6 @@ guojianglive.com guojiayikao.com guojiayixuekaoshi.com guojiazhiyezige.com -guojibk.com guojiexpress.com guojijinjia.com guojimami.com @@ -40095,6 +40832,7 @@ guoocang.com guopika.com guoping123.com guopop.tech +guoqi.org guoqi365.com guoqinwang.com guorongfei.com @@ -40148,6 +40886,7 @@ guozhenyi.com guozhivip.com guozhuan.com guozhuangxincai.com +guozi.org guoziyx.com gupang.com gupaoedu.com @@ -40241,6 +40980,7 @@ gwidc.com gwin-cn.com gwinbody.com gwjakzzx.com +gwjinan.com gwm-global.com gwmapp-h.com gwmapp-w.com @@ -40302,6 +41042,7 @@ gx81jxzl.com gx966888.com gxaas.net gxabd.net +gxacjs.com gxacto.com gxadt.com gxadwh.com @@ -40407,6 +41148,7 @@ gxceg.com gxcei.com gxcerc.com gxcexxzx.com +gxcfjx.com gxcgmf.com gxcgzh.com gxch168.com @@ -40436,6 +41178,7 @@ gxclzz.com gxcmgr.com gxcmicc.com gxcncec.com +gxcnjx.com gxcodemaker.com gxcors.com gxcounty.com @@ -40618,6 +41361,7 @@ gxfzpg.com gxfzyjs.com gxg1978.com gxgaia.com +gxgangji.com gxgangxin.com gxgangyuan.com gxganhua.com @@ -40966,6 +41710,7 @@ gxjiefeng.com gxjien.com gxjigeng.com gxjigeyuan.com +gxjihu.com gxjinan.com gxjingsheng.com gxjinhai.com @@ -41081,6 +41826,7 @@ gxkehong.com gxkelite.com gxkesui.com gxkfl.com +gxkfy.com gxkhjt.com gxkhkj.com gxkhn.com @@ -41142,7 +41888,6 @@ gxldzn.com gxlecheng.com gxlek.com gxlelin.com -gxlepanjun.com gxlfzl.com gxlgwl.com gxlgxx.com @@ -41270,6 +42015,7 @@ gxmqhb.com gxmscbs.com gxmsg.com gxmsgy.com +gxmtzn1658.com gxmuch.com gxmuwmfy.com gxmuyfy.com @@ -41285,6 +42031,7 @@ gxmzcl.com gxmzjwy.com gxmzkj.com gxmzly.com +gxmznzs.com gxnantong.com gxnapd.com gxnccq.com @@ -41571,6 +42318,7 @@ gxsut.com gxsuyun.com gxswgd.com gxswsw.com +gxswzps.com gxsxbj.com gxsxhw.com gxsy.org @@ -41610,6 +42358,7 @@ gxtjksj.com gxtjsnzg.com gxtk.com gxtkzx.com +gxtlgd.com gxtljnhb.com gxtltzjt.com gxtmh.com @@ -41671,6 +42420,7 @@ gxwenlian.com gxwenyutech.com gxwgdq.com gxwgjf.com +gxwhsy.com gxwhwy.com gxwjkj.com gxwjs.com @@ -42537,7 +43287,6 @@ gzsg.org gzsgczb.com gzshaudio.com gzsheraton.com -gzshik.com gzshiran.com gzshuangbao.com gzshuimh.com @@ -42698,6 +43447,7 @@ gzytgf.com gzyucai.com gzyucai.net gzyxedu.net +gzyxls.com gzyxtech.com gzyycg.com gzyyjt.net @@ -42713,6 +43463,7 @@ gzzdkg.com gzzdqy.net gzzfrl.com gzzgresin.com +gzzhanmei.com gzzhitu.com gzzhixin.com gzzhrong.com @@ -42810,6 +43561,7 @@ h5uc.com h5util.com h5video.shop h5wap.com +h5war.com h5youxi.com h5yunban.com h5zhifu.com @@ -43004,8 +43756,6 @@ haimawan.com haimeec.com haimi.com haimian.com -haimianbandstatic.com -haimianyinyue.com haimin.net haimini.com haimosic.com @@ -43328,6 +44078,7 @@ hanmeilin.com hanmembrane.com hanmozhai.com hanniulaser.com +hannor.com hannto.com hanon.cc hanpian.tv @@ -43576,6 +44327,7 @@ haoling.cc haolints.com haoliv.com haolizi.net +haolongsci.com haolvbt.com haolyy.com haoma.com @@ -43675,7 +44427,6 @@ haowanyou.com haowen100.com haowenbo.com haowenshi.com -haowenti.net haowu.com haowu.link haowusong.com @@ -43716,6 +44467,7 @@ haoyuanxiao.com haoyue.com haoyue28.com haoyuepu.com +haoyun.life haoyun13.com haoyun56.com haoyunbb.com @@ -44059,6 +44811,7 @@ hbjxad.com hbjxjy.org hbjxjyw.com hbjxym.com +hbjy.net hbjyjmzg.com hbjzgw.com hbjzxh.com @@ -44186,6 +44939,7 @@ hbszjsjt.com hbszsv.com hbsztv.com hbszxyjhyy.com +hbszzk.com hbszzx.com hbtcmu.com hbtcw.com @@ -44201,6 +44955,7 @@ hbtycp.com hbtycyjt.com hbtyxh.com hbvtc.net +hbwanhao.com hbwanrun.com hbweid.com hbwenchang.com @@ -44329,6 +45084,7 @@ hcj1952.com hcjike.com hcjt.cc hckg.com +hckj.org hclonely.com hclouder.com hcmiraefund.com @@ -44506,6 +45262,7 @@ he2d.com heacn.net head-way.com headconsultant.com +header.cc heading123.com headingfilter.com headphoneclub.com @@ -45036,6 +45793,7 @@ hewukeji.com hexaamc.com hexagonsemi.com hexchip.com +hexdo.com hexgears.com hexi-electric.com hexian.ltd @@ -45056,6 +45814,7 @@ hexsen.com hextower.com hexun.com hexun.com.tw +hexuncaizhi.com hexuu.com heyang.cc heyang365.com @@ -45223,6 +45982,7 @@ hfzjxh.com hfzkgw.com hfzls.com hfzssw.com +hg-automation.com hg-capacitor.com hg-daigou.com hg1218.com @@ -45275,6 +46035,7 @@ hgsyiyuan.com hgto.cc hgty5533.com hgty663.com +hgty7598.com hguduo.com hgvalve.com hgveejt270.vip @@ -45407,12 +46168,12 @@ hiao.com hiapk.com hiascend.com hiascend.net +hiavr.com hibace.com hibace.net hibbw.net hibelarus.com hibgm.com -hibianli.com hibixin.com hibogroup.com hibor.net @@ -45613,6 +46374,7 @@ hiram1009-dw.org hirata-cn.com hirata-hts.com hirede.com +hiregex.com hireye.com hirige.com hirisemed.com @@ -45695,6 +46457,7 @@ hixuexiao.net hiyd.com hiyongche.com hiyouib.com +hiyouqu.com hiyun.site hiyun.tv hizaoan.com @@ -45751,6 +46514,7 @@ hjksjx.com hjlrw.com hjlw.com hjmarathon.com +hjmtzb.com hjpcc.com hjpp.net hjr-exchange.com @@ -45861,6 +46625,7 @@ hkwb.net hkwzig.com hkxbjt.com hkxen.com +hkxtech.org hky360.com hkyukai.vip hkyxfgs.com @@ -45918,6 +46683,7 @@ hljgzkj.com hljhgckj.com hljiasen.com hljjjb.com +hljjkfp.com hljjytkj.com hljpm.com hljpost.com @@ -45947,6 +46713,7 @@ hlo-trade.com hlqiaojia.com hlread.com hlsdq.com +hlsimu.com hlstlyy.com hltmsp.com hltx.net @@ -46209,6 +46976,7 @@ hnktzz.com hnkxy.com hnkygas.com hnkzy.com +hnlantian.com hnlat.com hnlawyer.org hnlbdl.com @@ -46507,7 +47275,6 @@ holmesian.org holoalpha.com holsauto.com holteksupport.com -holy58.com holymalls.com holyxiongan.com home0311.com @@ -46727,6 +47494,7 @@ hongyue.com hongyugroup.com hongyugz.com hongyumedical.com +hongyunduo.com hongyuntx.com hongyuzps.com hongze.net @@ -46839,6 +47607,7 @@ hosgs.com hoshinesilicon.com hoshiroko.com hosjoy.com +hoslen-sh.com hosno7.com hosonglass.com hosory.com @@ -47429,6 +48198,7 @@ htudns.com hturl.cc htv123.com htvaas.com +htvcentral.com htwcq.com htwx.net htx.cc @@ -47524,6 +48294,7 @@ huafens.com huafu.com huagolf.com huagongkaoyan.com +huagujs.com huaguoshan.com huahai-gz.com huahaibiochem.com @@ -47806,6 +48577,7 @@ huaruicom.com huaruidns.com huaruisales.com huas.co +huasadata.com huash.com huashangcbbtv.com huashangedu.com @@ -47894,6 +48666,7 @@ huaweidun.com huaweifile.com huaweiita.com huaweils.com +huaweimall.com huaweimarine.com huaweimossel.com huaweioneaccess.com @@ -47913,6 +48686,7 @@ huaxi.net huaxi100.com huaxia.com huaxia77.com +huaxiabeidou.com huaxiaeye.com huaxiagg.com huaxiajianyan.com @@ -48552,6 +49326,7 @@ huoshanzhibo.com huoshanzhibo.net huoshanzhufang.com huoshen.com +huoshen99.com huoshi.shop huosu.com huotan.net @@ -48583,6 +49358,11 @@ hupu.com hupu.io hupucdn.com hupun.com +hur05100kns.com +hur05101kns.com +hur05111kns.com +hur05120kns.com +hur05121kns.com hurom.vip hurricane618.me hurricanechip.com @@ -48868,6 +49648,7 @@ hxsyjt.net hxt5.com hxtk.com hxtrip.com +hxtzgroup.com hxwglm.com hxx.net hxxkw.org @@ -49552,6 +50333,7 @@ hzzhuode.com hzzhyl.com hzzhzm.com hzzjjsgc.com +hzzjzk.com hzzkj.net hzzp.com hzzpko3186.vip @@ -49717,6 +50499,7 @@ iaocwbk.com iaoligame.com iaoyou.com iaozi.com +iaozu.com iape-js.com iapijy.com iapkk.com @@ -49763,6 +50546,7 @@ ibaobay.com ibaoji.com ibaossl.com ibaotu.com +ibaoxiu.net ibaralioho.com ibashu.net ibayapp.com @@ -50040,6 +50824,7 @@ icomuimi.com iconntech.com iconsapi.com icoolby.com +icoonal.xyz icoou.com icoremail.net icos8.com @@ -50227,6 +51012,7 @@ idianshijia.com idiaoyan.com idigi.net idingju.com +idlegog.com idloves.com idlpc.win idmchina.net @@ -50322,6 +51108,7 @@ iemailforce.com iemblog.com iemiq.com iemnet.xyz +iemouepk.com ienglish.store ienjoys.com ienjoys.mobi @@ -50469,7 +51256,6 @@ igelou.com igenetech.com igengmei.com igeshui.com -igetcool.com igetget.com igetmall.net igetui.com @@ -50483,6 +51269,7 @@ igo180.com igocctv.com igome.com igomkt.com +igoodgame.com igooma.com igou.com igoyx.com @@ -50541,6 +51328,7 @@ ihasl.com ihdt.tv ihdty.com ihealthcoming.com +ihei5.com iheima.com ihello.cc ihelpy.net @@ -50754,7 +51542,6 @@ ikuju.com ikunac.com ikuncdnfb.com ikunpay.com -ikutu.com ikx.me ikzybf.com il8r.com @@ -50774,6 +51561,7 @@ ilawpress.com ilazycat.com ilcweloc.com ilea-roswell.org +ileadsun.com ileapcloud.com ileci.com ileedarson.com @@ -50802,7 +51590,6 @@ illl.xyz ilmgq.com ilohas.com iloli.bid -iloli.love iloli.xin ilongre.com ilovechao.com @@ -51049,7 +51836,6 @@ imsle.com imsun.net imsyy.com imtaweb.net -imtedge.com imtics.com imtmp.net imtrwqjier3.com @@ -51203,6 +51989,7 @@ infzm.com ing10bbs.com ingageapp.com ingags.com +ingaoyt.biz ingcore.com ingdan.com ingeek.com @@ -51232,7 +52019,6 @@ initbb.com initcc.com initdd.com initff.com -initgg.com initialview.com initjj.com initkk.com @@ -51273,7 +52059,6 @@ inliuzhou.com inluckcalendar.com inmeng.net inmense.site -inmis.com inmuu.com inmyshow.com innatek.com @@ -51294,7 +52079,6 @@ innoplayfun.com innopro.cc innos.com innoscience.com -innosystemtech.com innotechx.com innotek-co.com innotimegood.com @@ -51471,6 +52255,7 @@ iopiopl.com ioppo.net ioptics.org iorca.net +iornnl.xyz ios114.com ios222.com ios98.com @@ -51526,7 +52311,6 @@ ip181.com ip192.com ip33.com ip3366.net -ip3a.com ip518.com ip66.com ip66.net @@ -51742,6 +52526,7 @@ irisgw.net irising.me irlianmeng.com irmleader.com +irober.com irobotbox.com irockbunny.com iroing.com @@ -51815,6 +52600,7 @@ ishangu.com ishansong.com ishaohuang.com isharepc.com +isheely.com isheji.com isheji5.com ishell.cc @@ -51971,7 +52757,6 @@ itakeeasy.com italyvisacenterd.com itamt.com itangbole.com -itangsoft.com itangyuan.com itanlian.com itany.com @@ -52426,6 +53211,7 @@ izatcloud.net izazamall.com izb.pub izdatatech.com +izenith.co izestchina.com izgfu.com izhailong.com @@ -52661,6 +53447,7 @@ jcashore.com jcbjbcak.com jcbmt.com jccchina.com +jcceram.com jccinema.com jccjtwl.com jccpay.com @@ -52874,6 +53661,7 @@ jdfmgt.com jdfryl.com jdfschool.com jdfw1.com +jdfybjy.com jdfzm.com jdgogo.com jdgsgl.com @@ -53066,7 +53854,6 @@ jfbuilding.com jfchinese.com jfcjt.com jfcoo.com -jfcoop.com jfdaily.com jfedu.net jfgjwl.com @@ -53767,7 +54554,6 @@ jiemin.com jiemo.net jiemodui.com jiemoselect.com -jiemu5.com jienyl.com jiepai.net jiepaids.com @@ -53777,6 +54563,7 @@ jieqi.com jieqian.co jieqibg.com jieqinwang.com +jierengz.com jieri2.com jierili.com jieruchaosheng.com @@ -53794,6 +54581,7 @@ jietu365.com jietuhb.com jietuosh.com jietusoft.com +jieweijt.com jiewen.run jiexi.net jiexitz.com @@ -53857,6 +54645,7 @@ jijiang5.com jijiangep.com jijianzhineng.com jijiaoyu.com +jijidi.com jijie168.com jijigugu.club jijijijin.com @@ -53973,6 +54762,7 @@ jindaixx.com jindaoshangwu.com jindati.com jinde-logistics.com +jindianweb.com jindidata.com jindidq.com jinding.cc @@ -54115,6 +54905,7 @@ jingshun-wl.com jingshzh.com jingsocial.com jingtai-group.com +jingtang.xyz jingtanggame.com jingtao58.com jingtuitui.com @@ -54161,6 +54952,7 @@ jingyuetang.com jingyunos.com jingyunyilian.com jingyuweike.com +jingyuyun.com jingzhengu.com jingzhi5.com jingzhouxw.com @@ -54253,6 +55045,7 @@ jinmaowy.com jinmaozs.com jinmeiji.com jinmenrc.com +jinmi.com jinming.net jinmixuetang.com jinmogame.com @@ -54280,6 +55073,7 @@ jinquanpharm.com jinqunla.com jinr.com jinridiaoyu.com +jinriguanzhu.cc jinrirm.com jinrishici.com jinritemai-inc.com @@ -54441,6 +55235,7 @@ jishux.com jishuzf.com jishuzhan.net jisi17.com +jisikaer.com jissbon.com jisu-cnd.com jisuanke.com @@ -54571,6 +55366,7 @@ jiurichem.com jiurong.com jiuruigroup.com jius.net +jiusanedu.com jiuse.cloud jiuse2.cloud jiuse3.cloud @@ -54747,6 +55543,7 @@ jjhlkqn.com jjhuifu.com jjhuolang.com jjhuoyan.com +jjhza.win jjidc.com jjiehao.com jjinfo.com @@ -54764,6 +55561,7 @@ jjldbk.com jjldxz.com jjledu.net jjlhbs.com +jjlvu.com jjlysh.com jjmatch.com jjmfc.com @@ -54966,7 +55764,6 @@ jlsegx.com jlsemi.com jlsfcyy.com jlsgjt.com -jlshuiyuan.com jlsjsjlhgczxxh.com jlsjsxxw.com jlsports.com @@ -55296,6 +56093,7 @@ joojcc.com joojtech.com joojzz.com joomla.work +joomobx.com joooz.com joowhee.com joox.com @@ -55448,6 +56246,7 @@ jpxww.com jpyoo.com jpzx.net jpzy01.com +jq-school.com jq22.com jq33.com jqbar.com @@ -55815,6 +56614,7 @@ jskhfm.com jskjcms.com jskjgc.com jskjgroup.com +jsklcy.com jskly.com jskoso.com jskpcg.org @@ -55939,6 +56739,7 @@ jsshuntai.com jssia.org jssihuan.com jssjchyxh.com +jssjiu.com jssjrfw.com jssjxgyw.com jssks.com @@ -56552,6 +57353,7 @@ juxinhuizhi.com juxuan.net juxuewen.com juyang-chem.com +juyanwenjuan.com juyebwg.com juyingele.com juyingonline.com @@ -56662,6 +57464,7 @@ jxcat.com jxcb.net jxcc.com jxccb.com +jxcdkjfz.com jxcfs.com jxchaguan.com jxckd.shop @@ -56692,6 +57495,7 @@ jxep.net jxetv.com jxey.com jxfeng.com +jxfhomes.org jxfls.com jxfrjs.com jxfxky.com @@ -56790,6 +57594,7 @@ jxsjypt.com jxslsyy.com jxsltz.com jxslyw.com +jxsrfdc.com jxsrjt.com jxsrra.com jxssjx.com @@ -56976,6 +57781,7 @@ jyyun.com jyzb01.com jyzc.com jyzhongg.com +jyzz666.com jyzzdq.com jyzzx.com jz-hy.com @@ -57341,6 +58147,7 @@ kang7.com kangai8.com kangame.tv kangantu.com +kangbaifoundation.com kangbatv.com kangbeijia.com kangbidz.com @@ -57378,6 +58185,7 @@ kanglisha.com kangnai.com kangpeining.com kangpugroup.com +kangq.com kangqiao-sh.com kangre.com kangrunpharm.com @@ -57950,7 +58758,6 @@ kfb163.com kfcbest.com kfcdn.com kfcjrl.com -kfcrm.com kfd3sm2c.com kfdcc.com kffone.com @@ -57994,10 +58801,10 @@ khotyn.com khqihuo.com khruiming.com khs1994.com -khvle.com khysct.com ki-pa.com kiaic.com +kiapmyf.xyz kibinggroup.com kickoffo.site kicontech.com @@ -58319,6 +59126,7 @@ klcsb.com kldhq.com klhpw.com klhuyan.com +klianfa.com klicen.com klingai.com klingxai.com @@ -58472,7 +59280,6 @@ koba8.com kobelco-zh.com kobox.tv kocla.com -kodakbanjara.com kodcloud.com koderover.com kodmp.com @@ -58486,6 +59293,7 @@ kohergroup.com koiclub.net koikreative.com koinocn.com +kojihjnklka.asia kojtech.com kok.plus kokojia.com @@ -58538,6 +59346,7 @@ kooaoo.com koobone.com koocdn.com kooci.net +koodi98.com koofang.com koofun.com koogua.com @@ -58562,6 +59371,7 @@ koplayer.com kopperchem.com koreabt.com koreavisa-wh.com +koreaxing.com korirl.com kormee.com kornsweets.com @@ -58699,6 +59509,7 @@ kscdns.com kschuangku.com kscloudapi.com ksco.cc +kscord.com ksdedu.com ksdhgy.com ksecit.com @@ -58759,6 +59570,7 @@ kstv.com ksupdate.com ksust.com kswcd.com +kswxjnjs.com ksx88.com ksxfgc.com ksxianda.com @@ -58846,6 +59658,7 @@ kuai7.com kuai8.com kuaiads.com kuaiapps.com +kuaibaobei.com kuaibiao2000.com kuaicad.com kuaicha365.com @@ -59012,6 +59825,7 @@ kuark.com kuashou.com kuayuegroup.com kubercloud.com +kubesre.xyz kubey.cc kubikeji.com kuboluo.com @@ -59100,7 +59914,6 @@ kunchuang.com kunduo.com kungfucloud.com kungfuenglish.com -kungfumars.com kunguankeji.com kunguanyx.net kunjuke.com @@ -59154,7 +59967,6 @@ kuocaicdn.com kuocaidns.com kuocaiyun.com kuoo8.com -kuos.xyz kuosheng.com kuosheng.net kuotu.com @@ -59188,6 +60000,7 @@ kusnc17.com kuso.xyz kusouji.com kut3974vc.com +kutinai.com kutj.com kutongji.com kutuan.com @@ -59664,6 +60477,7 @@ langxi.xyz langxingys.com langxiyuyou.com langzezs.com +lanh.love lanhanba.net lanhao.name lanhu.com @@ -60073,6 +60887,7 @@ lc-news.com lc-rc.com lc044.love lc123.net +lc1618.com lc787.com lcang.com lcatgame.com @@ -60307,6 +61122,7 @@ lechuangzhe.com lecloud.com lecloudapi.com lecloudapis.com +lecoinfrancais.org lecomposites.com lecong56.com leconiot.com @@ -60393,6 +61209,7 @@ leijue.net leijun.com leike.cc leikeji.com +leilei3dprinter.com leileiluoluo.com leilong158.com leimengjixie.com @@ -60980,6 +61797,7 @@ liao98.com liaobagua.com liaocheng.cc liaochuo.com +liaode.com.tw liaogu.com liaogx.com liaoing.com @@ -61121,6 +61939,7 @@ lifeeu.com lifefunkingdom.org lifegc.com lifegreenmedical.com +lifeng.in lifengshoe.com lifeofguangzhou.com lifesense.com @@ -61284,7 +62103,6 @@ linfan.com linfeicloud.com linfen365.com linfeng.tech -linfengnet.com linfenwater.net ling-shi.com lingangholding.com @@ -61352,6 +62170,7 @@ lingshenxl.com lingshi.com lingshimiyu.com lingshou.com +lingshulian.com lingshunlab.com lingsiqiwu.com lingsky.com @@ -61375,9 +62194,7 @@ lingxianfund.com lingxicloud.com lingxigames.com lingxing.com -lingxingerp.com lingxingkj.com -lingxingxinli.com lingxiuwenlv.com lingxmall.com lingy.cc @@ -61488,6 +62305,7 @@ linkvans.com linkvfx.com linkwebll.com linli580.com +linlikuaipao.com linliyz.com linlongyun.com linlongyx.com @@ -61536,6 +62354,7 @@ linuxba.com linuxbaike.com linuxbaodian.com linuxboy.net +linuxcool.com linuxde.net linuxdiyf.com linuxdot.net @@ -61548,6 +62367,7 @@ linuxidc.net linuxmi.com linuxpanda.tech linuxpk.com +linuxprobe.com linuxsir.com linuxso.com linuxtone.org @@ -61603,6 +62423,7 @@ liriansu.com liriji.com lirui.name lis99.com +lisenergy.com lisheng.gold lishengstone.com lishi-test.com @@ -61801,6 +62622,7 @@ liuzhoukaichuang.com liuzhousteel.com liuzhuni.com liuziyoudu.com +liuzongyang.com liuzy88.com livanauto.com live-helps.com @@ -61875,6 +62697,7 @@ liyi99.com liyinewmaterial.com liyingfei.com liyinka.com +liyu8.com liyuan1999.com liyuan99.com liyuan99.vip @@ -62427,6 +63250,7 @@ lookao.com lookbaby.com lookbravo.com lookchem.com +lookgame.com looking-car.com lookr.cc looktm.com @@ -62607,6 +63431,7 @@ lr-amm.com lr-link.com lrc99.com lrcb.net +lrceu.com lrdcq.com lrhold.net lricn.com @@ -62960,6 +63785,7 @@ luochenyl.com luochenzhimu.com luodian.com luoganpump.com +luogu.org luohanacademy.com luohanyu.cc luohuedu.net @@ -63148,6 +63974,7 @@ lvpai114.com lvpin100.com lvpu-chem.com lvqingqichangjia.com +lvrdn.com lvren.com lvruanhome.com lvsan.com @@ -63630,7 +64457,6 @@ lzoam.com lzolm.com lzorient.com lzpfei.com -lzpipi.com lzpmia.com lzppt.com lzprocess.com @@ -63715,6 +64541,7 @@ lzyxfs.com lzyxsoft.net lzyyy.com lzyz.org +lzyzbz.com lzyzx.net lzzcloud.net lzzf.com @@ -63740,6 +64567,7 @@ m1315.com m186.net m188.com m1905.com +m1938.com m1ok.com m1page.com m1world.com @@ -63748,7 +64576,6 @@ m2ez.com m2mlib.com m2mzy.com m2time.net -m2ucam.com m3-cloud.com m3cqjc3og6.com m3guo.com @@ -64046,6 +64873,7 @@ makeblock.com makedie.me makeding.com makeeu.com +makefang.com makefont.com makelie.com makelove.la @@ -64117,6 +64945,7 @@ mandao-tech.com mandarinedu.org mandarinhouse.com mandarinmorning.net +mandashu.com mande-car.com mandian.com mandiankan.com @@ -64653,7 +65482,6 @@ mcool.com mcpemaster.com mcpfks.com mcpmaid.com -mcpworld.com mcq93.app mcqy.net mcsafebox.com @@ -64686,6 +65514,7 @@ md-hmjt.com md-pms.com md5ma.com mdaehx.com +mdapp.tv mdbchina.com mdbimg.com mdclub.org @@ -64710,7 +65539,6 @@ mdmt.com mdnice.com mdouvip.com mdouwz.com -mdown.asia mdpda.com mdpetrotech.com mdrmyy.com @@ -64746,6 +65574,7 @@ mebhaoya.com mebo.com mebtf.com mebyk.com +mecaraiotp.org mechatim.com mechina.org mechr.com @@ -64815,6 +65644,7 @@ meetbf.com meetfave.com meethall.com meethome.com +meeting.so meeting666.com meetingmeet.com meetingrj.com @@ -64845,6 +65675,8 @@ megmeet-welding.com megobike.com megou8.com meguo.com +meguri.app +meguri.moe megvii-inc.com megvii.com meheco.com @@ -64956,6 +65788,7 @@ meiling.com meilishuo.com meilishuo.net meilisite.com +meiliwang.org meiliworks.com meiljiaqi.com meilunmeijia.com @@ -65008,11 +65841,11 @@ meishi.cc meishi13.com meishichina.com meishiffx.online +meishij.net meishijr.com meishilife.com meishiqin.com meishischool.com -meishitx.com meishiwangluo.com meishizuofa.com meishow.com @@ -65085,6 +65918,7 @@ meituxiaopu.com meituxiu.com meituxiuxiu.com meituyidong.com +meituyuan.com meituyun.com meiwafuying.com meiweibf.com @@ -65243,6 +66077,7 @@ merlinmedicine.com merries-china.com merroint.com merryhome.com +merucario.com mescroll.com meshiot.com mesince.com @@ -65333,6 +66168,7 @@ mfhcd.com mfinetech.com mfisp.com mfjl.wiki +mfjxcn.com mfk.com mflady.com mfniu.com @@ -65481,6 +66317,7 @@ mianyanghuida.com mianyaojsj.com mianyazulin.com mianzhouhotel.com +miao-lang.com miao15777790078.com miaobe.com miaobige.com @@ -65675,6 +66512,7 @@ migumaotrip.com migutv.com miguvideo.com miguyu.com +mihanw.com mihayo.com mihayou.com mihopacking.com @@ -65875,6 +66713,7 @@ mingshi58.com mingshiedu.com mingshisanling.com mingsoft.net +mingstar.net mingtaimed.com mingtaokeji.com mingtejia.com @@ -66195,6 +67034,7 @@ mlfkc.net mlgj.com mlgxw.org mlhimalayanxt.com +mlhld.com mlibaba.com mlibrarys.com mliev.com @@ -66424,6 +67264,7 @@ modb.cc modb.pro modelarts-infer.com modelarts-maas.com +modelevel.com modelltd.com modelones.com modelscope.ai @@ -66458,7 +67299,6 @@ moe.tips moe.xin moe123.com moe123.org -moe233.org moe321.com moeclub.org moedot.net @@ -66582,7 +67422,6 @@ molilier.com molimoli.tech moliplayer.com molipy.com -moliranran.com molishe.com molixiangce.com mollervilla.com @@ -66787,6 +67626,7 @@ mouralanco.com mouratoriousi.com mousesquadca.org mousycoder.com +mout.me moutaichina.com mova-tech.com movcam.com @@ -67250,6 +68090,7 @@ mwclg.com mwcloudcdn.com mwcloudcdn.info mwcname.com +mwdfawg.com mweda.com mwemp.com mwjournalchina.com @@ -67429,6 +68270,7 @@ mydigit.net mydigitex.com mydisk.work mydnns.com +mydns8.com mydnser.com mydnspod.net mydnsw.com @@ -67448,7 +68290,6 @@ myezdns.com myfans.cc myfdmg.com myfhospital.com -myfilm.cc myfrfr.com myfun7.com myfund.com @@ -67471,7 +68312,6 @@ myhithink.com myhjw.vip myhkw.com myhl-china315.com -myhloli.com myhm.org myhomeok.com myhongzuan.com @@ -67614,10 +68454,8 @@ mysmth.net mysongktv.com mysore-yoga.com myspain.org -mysqil.com mysql360.com mysqlab.net -myssl-uri.com myssl.com mystarch.com mysteel.com @@ -67830,12 +68668,14 @@ name2012.com name321.net nameidi.com namejin.com +namepre.com nameserver.fit namesilodns.com nami.com namibox.com namichong.com namihair.com +namipan.com namiso.com namisoft.com namitiyu.com @@ -68258,6 +69098,7 @@ nd15.com nd56.com nd597.com ndapp.com +ndbzteck.com ndcimgs.com ndcjl.com ndcpp.com @@ -68623,6 +69464,7 @@ newxue.com newyifagroup.com newyishi.com newyorkerdownunder.com +newyorkluxurywatch.com newyoton.com newyx.net newzealandvscenter.com @@ -68740,6 +69582,7 @@ nhcec.com nhcilab.com nhcsw.com nhd-mart.com +nhdacrw.xyz nhdia.com nhdmd.com nhfyyy.com @@ -68822,6 +69665,7 @@ nicsbuy.com nicwind.com nidecms.com nidiandaojia.com +nie.io nieapps.com niegobrand.com niegoweb.com @@ -68955,7 +69799,6 @@ niujinniu.com niuk12020gi.com niukk.com niuming147.com -niumowang.com niunai.com niuniufund.com niupiano.com @@ -69199,6 +70042,7 @@ njjgjs.com njjgzlgl.com njjhjt.com njjht.com +njjiancai.com njjianguo.com njjiantian.com njjienuo.com @@ -69447,6 +70291,7 @@ njyqkj0ksyz.com njyqmj.com njyspharma.com njysw.com +njytian.com njytyy.com njyuanlang.com njyule.club @@ -69715,6 +70560,7 @@ nnjnwg.com nnjr.co nnjsdsoft.com nnjsgy.com +nnjsypx.com nnjsza.com nnjszlsb.com nnjt.com @@ -69828,6 +70674,7 @@ nnttsoft.com nntuobang.com nntxw.com nntzgz.com +nnups.com nnwb.com nnwhg.com nnwilking.com @@ -69921,6 +70768,7 @@ nodeing.com nodejs999.com nodekey.com nodeme.site +nodepacific.com nodpcba.com noeic.com nohost.pro @@ -70086,6 +70934,7 @@ noxpadelsale.com noxxxx.com noyes88.com np176.com +npbbs.net npbeta.com npc233.com npedi.com @@ -70484,6 +71333,7 @@ nzbdw.com nzcxh.com nzggroup.com nzhnb.com +nziku.com nzkd.com nzl09250oa.com nzlw.com @@ -70663,7 +71513,6 @@ oemsoc.download.prss.microsoft.com oemsocuat.download.prss.microsoft.com oemssl.cn.cdn.cloudflare.net oemxitong.com -oend.net oeob.net oepkgs.net oepkgs.org @@ -70907,6 +71756,7 @@ omsheji.com omsmy.com omso2o.com omycar.cc +omyerp.com on-sun.com onaliyun.com oncanyin.com @@ -70931,6 +71781,7 @@ onebox.site onebrowser.net onebuygz.com onecoder.site +onecommunity-asia.com oneconnectft.com onedi.net onediankeji.com @@ -71028,6 +71879,7 @@ onlylady.com onlyling.com onlyliuxue.com onlymr.com +onlypat.com onlyred.net onlyrobotedu.com onlyrubberparts.com @@ -71043,7 +71895,6 @@ ontheroadstore.com onthetrip.com onthink.com onwaf.com -onwaytech.com onwear.net onwork.com onwsw.com @@ -71060,6 +71911,7 @@ oocheoo.com ooclab.com oofont.com oogcw.com +oogps.com oogsy.com oohdear.com ooiii.com @@ -71252,7 +72104,6 @@ orayer.com orayimg.com orbiepharm.com orbitmes.com -orbrand.com orcadt.com orchome.com orcode.com @@ -71434,6 +72285,7 @@ oulagongshi.com oulehdtv.com oulgp.com ouliwang.com +oulongauto.com ouluwind.com ouluyun.com oulvnet.com @@ -71456,6 +72308,7 @@ oupeng.com oupengcloud.net oupula.com oupuzw.com +ouqo05161luj.com our360vr.com ourai.ws ourail.com @@ -71641,6 +72494,7 @@ p-pass.com p-sig-dom.com p-sig-dom.net p.biz +p.cdn.persaas.dell.com p023.com p0371.com p0431.com @@ -71762,6 +72616,7 @@ paizhaofanyi.net paizhe.com paizi.com paizi.net +paizia.com paizin.com paizishop.com pajkb.com @@ -72331,6 +73186,7 @@ pengyou.com pengyou.net pengyoukan.com pengyuanled.com +pengyucpa.com pengyuwei.net pengzhihui.xyz penhuijiqi.com @@ -72363,7 +73219,6 @@ perfcode.com perfect-input.com perfect99.com perfectdiary.com -perfectlingo.com perfecttradinghk.com perfectwatchen.com perfertw.com @@ -72389,6 +73244,7 @@ petctchina.com petctw.com peter-zhou.com peterjxl.com +petersonjace.shop petersonlian.com petgugu.com pethr.com @@ -72421,7 +73277,6 @@ pfwx.com pfzhiliao.com pg-leak.com pg114.net -pg245348.cc pgbee.com pgc.tv pgcaststone.com @@ -72627,6 +73482,7 @@ picc.com piccamc.com picchealth.com piccjs.com +picdlb.com picdns.com picing.com pickme.cc @@ -72814,6 +73670,7 @@ pinyafang.com pinyinxiang.com pinyuan.cc pinyuew.com +pinzhi.org pinzhi365.com pinzhikeji.net pinzs.com @@ -72869,11 +73726,13 @@ pj09.com pj334.org pj39800.com pj50.com +pj57.com pj62049.com pj73.com pjbest.com pjf.name pjgear.com +pjgjg.com pjhome.net pjhubs.com pjjyzx.com @@ -72970,6 +73829,7 @@ playcomet.jp playcool.com playcrab.com playerinfinite.com +playezu.com playfifa.com playgm.cc playlu.com @@ -73510,6 +74370,7 @@ promise-sz.com promisingedu.com pronax.tech pronetway.com +prophecyi.com proposalt.com propsad.com pros-view.com @@ -73540,6 +74401,7 @@ pryk0755.com przhushou.com przwt.com ps-boat.com +ps123.net ps12345.com ps173.com ps288.com @@ -73791,7 +74653,6 @@ pw-partners.com pw1999.com pw88.com pwand.com -pwcinema.com pwdding.icu pwithe.com pwjptdg.xyz @@ -73903,6 +74764,7 @@ pzhdahe.com pzhedu.com pzhl.net pzhol.com +pzhqx.com pzhqz.com pzhr.com pzhwater.com @@ -73976,6 +74838,7 @@ qbjrxs.com qbjwater.com qbox.me qbox.net +qbtxt.net qbtzjt.com qbview.com qbxz.com @@ -74164,6 +75027,7 @@ qdmama.net qdmcxh.com qdmm.com qdmswx.com +qdn4ydsg3s.com qdnsyh.com qdnxsyy.com qdnzrmyy.com @@ -74290,7 +75154,6 @@ qgwzjs.com qgxl.org qgyqshz.com qgysj.org -qgyyl.com qgyyzs.net qgzb.net qgzxx.com @@ -74417,6 +75280,7 @@ qianbao.com qianbaocard.com qianbaogroup.com qianbaohr.com +qianbeibei.shop qianbi360.com qianbianapi.com qianbianma.com @@ -74525,6 +75389,7 @@ qianrenge.com qianrihong.net qianrong.me qianselight.com +qianshanren.com qianshengqian.com qianshuixing.com qiansiw.com @@ -74661,6 +75526,7 @@ qidi-edu.com qidian-cos-backup.com qidian-cos.com qidian.com +qidian688.com qidianbox.com qidianjob.com qidianla.com @@ -74716,6 +75582,7 @@ qiguoread.com qihanbio.com qihangcrrc.com qihaoip.com +qihaoqu.com qihaxiaoshuo.com qihejy.com qihepaimai.com @@ -74925,7 +75792,6 @@ qingmuxinli.com qingnianlvxing.com qingniantuzhai.com qingnianwang.com -qingning88.com qingpanduola.com qingpin.vip qingping.co @@ -74960,7 +75826,6 @@ qingtian16265.com qingtiancms.net qingting.fm qingting123.com -qingtingai.net qingtingfm.com qingtingip.com qingtingjh.com @@ -74975,7 +75840,6 @@ qingxinjs.com qingxuetang.com qingxun.com qingyantek.com -qingye.org qingyeyun.com qingyi.com qingying.net @@ -75530,6 +76394,7 @@ qqhn.net qqhot.com qqhrnews.com qqhubei.com +qqhwb.com qqimage.net qqimg.net qqjapan.com @@ -75766,6 +76631,7 @@ qu67.com qua.com quaita.com qualcomm.cn.cdn.cloudflare.net +quality666.com qualtekgz.com quan.mx quan007.com @@ -75815,6 +76681,7 @@ quanoukeji.com quanqiuwa.com quanqiuweishang.com quanquanapp.net +quanriai.com quansheng-group.com quanshi.com quanshuge.com @@ -76248,6 +77115,7 @@ quthing.com qutianshanav.com qutingting.com qutoutiao.net +qutouwang.com qutu.com qutuancan.com qutuiwa.com @@ -76364,6 +77232,7 @@ qxndt.com qxnecn.com qxnic.com qxnzx.com +qxozm.com qxqing.com qxqxa.com qxrcw.com @@ -76462,6 +77331,7 @@ qywater.net qywcom.com qywd.com qywww.net +qyx888.com qyxa-tencentclb.cloud qyxa-tencentclb.com qyxa-tencentclb.net @@ -76553,6 +77423,7 @@ qzrc.com qzrcsc.com qzrcw.com qzread.com +qzrencai.com qzsdkj.com qzsfy.com qzsgyxx.com @@ -76830,6 +77701,7 @@ rc168.com rc3cr.com rc775.com rcbc888.com +rcbuying.com rccaijing.com rccchina.com rcche.com @@ -77337,6 +78209,7 @@ ribio.com ribolia.com ricequant.com riceyun.com +rich-chang.com rich-fc.com rich-futures.com rich-healthcare.com @@ -77491,6 +78364,7 @@ rkgaming.com rkkgyy.com rklive888.com rksec.com +rkvir.com rkzxhyy.com rl-consult.com rl1vypwyd2.com @@ -77705,6 +78579,7 @@ roosur.com root-servers.world root1111.com rootcloud.com +rootguide.org rootintech.com roouoo.com ropefitting.com @@ -77725,6 +78600,7 @@ rosesandgold.fun rosewin.com rosin-china.com rosirs-edu.com +rosmontis.com rosnas.com rosoo.net rossoarts.com @@ -77800,7 +78676,6 @@ rrioo.com rrjc.com rrkf.com rrky.com -rrmj.plus rrmj.tv rrppt.com rrrdai.com @@ -78048,7 +78923,6 @@ ruixuncw.com ruixunidc.com ruixunidc.net ruiyang-ra.com -ruiying.online ruiying3d.com ruiyingkeji.com ruiyitech.com @@ -78166,7 +79040,6 @@ ruubypay.com ruvar.com ruwen5.org ruxiaoyi.com -ruyi.ai ruyig.com ruyigou.com ruyile.com @@ -78315,6 +79188,7 @@ s2cinc.com s2g2b2c.com s361.com s369.com +s3s9.com s4g5.com s4y6lhotaw.com s4yd.com @@ -78364,6 +79238,7 @@ safbon.com safdsafea.com safeb2b.com safecenter.com +safedocumentview.com safeglp.com safehoo.com safejmp.com @@ -78451,6 +79326,7 @@ sajs.com sakata.one saki.cc sakura.vin +sakura286.ink sakway.com saky.site salabs.org @@ -78700,6 +79576,7 @@ saolife.com saomadc.com saomadiancan.com saoniuhuo.com +saop.cc saoshu.org saoso.com saowen.net @@ -78753,6 +79630,7 @@ sbjd88.com sbjxyq.com sbk-h5.com sbkh5.com +sbl-ruseldau.com sblseceorf.com sblsechuorb3.net sblsecorgan.com @@ -78808,6 +79686,7 @@ scanv.com scarbbs.com scarclinic-cn.com scardtech.com +scasqhwz.com scatc.net scavc.com scbaidu.com @@ -79355,6 +80234,7 @@ sddory.com sddp.net sddstore.com sddtzx.com +sdduojing.com sddyzl.com sddyzy.com sddzinfo.com @@ -79404,6 +80284,7 @@ sdglql.com sdgmjiaoyun.com sdgsgcjsjt.com sdgsjc.com +sdgslb.com sdgswl.com sdgsxcljt.com sdgsxxjt.com @@ -79650,12 +80531,14 @@ sdtcck.com sdtdata.com sdtdfz.com sdteleiptv.com +sdtender.com sdteya.com sdtianjing.com sdtkgl.com sdtlpm.com sdtlyyjt.com sdtryyjt.com +sdtvjiankang.com sdtwxx.com sdtxmq.com sdtylq.net @@ -79922,6 +80805,7 @@ seijoin.com seilway.com seily.com seirobotics.net +seis-jun.xyz seisman.info sejai.com sejianghu.com @@ -79978,6 +80862,7 @@ sendijt.com sendong.com sendpioneer.com sends.cc +sendsharedfiles.com senfa.net senge-dq.com senge.com @@ -79998,6 +80883,7 @@ senmiaoliuxue.com senmiaoschool.com senorsen.com senpet.com +senphoto.org senpian.com senra.me sense-hk.com @@ -80122,10 +81008,10 @@ sf-laas.com sf-pay.com sf-saas.com sf-zs.net -sf.global sf007.com sf024.com sf2021.com +sf34.com sf7.net sf888.net sfacg.com @@ -80155,6 +81041,7 @@ sfitcdp.com sfjddna.com sfjdml.com sfjsgroup.com +sfjt.co sfjx888.com sfkedu.com sfkj-tech.com @@ -80399,6 +81286,7 @@ shaanyaogroup.com shabc.com shabc.net shabidding.com +shabiyun.com shachong8.com shacumox.com shadafang.com @@ -80477,6 +81365,7 @@ shangate.com shangbangzhuan.com shangbanla.net shangbanzugroup.com +shangbaolai.com shangbiao.com shangbiao.store shangbiaocheng.com @@ -80662,6 +81551,7 @@ shanliaoapp.com shanling.com shanlinjinrong.com shanlink.com +shanlinshengwu.com shanliulian.com shanmeixincai.com shanniaorecycle.com @@ -80934,7 +81824,6 @@ shejiyue.com sheketiandi.com shekou.com shelive.net -shell.cdn.office.net shellpub.com shellsec.com shelter-china.com @@ -81193,6 +82082,7 @@ shezaixian.com shezfy.com shfa120.com shfamily.com +shfangshui.com shfayy.com shfcw.com shffjt.com @@ -81435,6 +82325,7 @@ shineoptics.com shineray-motors.com shinerayad.com shinescan.tech +shinetsu.shop shineu.com shineway.com shinewaytech.com @@ -81780,7 +82671,6 @@ shouhuigroup.com shouhuisoft.com shouhuojiyun.com shouhuola.com -shouji shouji.com shouji.racing shouji10086.com @@ -82193,6 +83083,7 @@ shulex-voc.com shuliangtec.com shulidata.com shuling.vip +shulink.com shuliyun.com shuma7.com shumaidata.com @@ -82255,6 +83146,7 @@ shuoji1688.com shuojuchaxu.com shuoman.com shuomingshuku.com +shuoshuojie.net shuoshuojuzi.com shuoshuokong.com shuoshuokong.org @@ -82688,7 +83580,6 @@ simuwang.com simwe.com simxhs.com sin0sites.com -sina sina-cf.com sina.com sina.lt @@ -82731,6 +83622,7 @@ sinf-test.net sinf.net sinflb.net sinforcon.com +sinfotek.com singaporepaya.com singbon.com singchy.com @@ -82948,6 +83840,7 @@ sintu.com sinture.com sinvofund.com sinvta.com +sinyoung.site sinyuan.com sinzk.com siobp.com @@ -83170,6 +84063,7 @@ sjz-marathon.com sjz.cc sjz110.cc sjz44z.com +sjz7.com sjzbwx.com sjzcmw.com sjzez.com @@ -83254,6 +84148,7 @@ sktecfibre.com skuking.com skusoft.com skwfls.com +skxox.com skxsj.com sky-deep.com sky-fire.com @@ -83683,6 +84578,7 @@ snibe.com sniec.net snieri.com snimay.com +sniuw.com snjjiu.com snjnationalpark.com snjt.com @@ -84020,7 +84916,6 @@ sooal.com sooauto.com soochowchem.com soocor.com -sood6.com sooele.com soofel.com soogf.com @@ -84746,7 +85641,6 @@ staticfile.net staticfile.org statickksmg.com statics.cc -statics.teams.cdn.office.net staticsdd.com staticsoe.com staticsoem.com @@ -84811,10 +85705,10 @@ stemceltech.com step-by-step.tech step-motor.com stepelectric.com -stepfun.com stepsigriner.com stereorise.com steriguardmed.com +stexc.online stfile.com stg8.com stgiantfilms.com @@ -85385,6 +86279,7 @@ supercalnow.com supercarrier8.com supercrm.com superdns.org +superepoxyresin.com superfix.com superhcloud.com superic.com @@ -85451,8 +86346,8 @@ suqun-group.com surely.cool surerp.com surface.download.prss.microsoft.com -surface.downloads.prss.microsoft.com surfacetreatmentgroup.com +surfacetreatmenttechnology.com surfcg.com surge.run surgerii.com @@ -85630,6 +86525,7 @@ swjsj.com swjtjt.com swkedou.com swkong.com +swkpe.tw swliuxue.com swltools.com swnic.com @@ -85864,6 +86760,7 @@ sxxfj.com sxxjdz.com sxxl.com sxxrmyy.com +sxxsmjh.com sxxt.net sxxw.net sxxyfw.com @@ -85966,7 +86863,6 @@ syitgz.com syjcly.com syjcxh.com syjiancai.com -syjiaotong.mobi syjkqzw.com syjlp.com syjsq.com @@ -86237,6 +87133,7 @@ szbayy.com szbazx.com szbbs.org szbcase.com +szbcfurniture.com szbdyd.com szbdzs.com szbeilu.com @@ -86633,6 +87530,7 @@ sznj91.com szns-marathon.com sznsyy.net sznumberone.com +sznwriterhouse.work sznyfz.com sznyyznnj.com szobk.com @@ -86914,6 +87812,7 @@ szyptx.net szyqjzxh.com szyran.com szys.net +szys119.com szysjt.com szysmpay.com szystea.com @@ -87001,6 +87900,7 @@ t3yanzheng.com t47888.com t49cdn.com t4z5p5s9k4q3m4h4m3.org +t5.work t50888.com t528.com t56.net @@ -87274,6 +88174,7 @@ tangsanshu.com tangsem.com tangshan-marathon.com tangshanliulin.net +tangshuang.net tangshui.net tangtang.org tanguan.net @@ -87432,6 +88333,7 @@ taopuwang.com taopxx.com taoq.net taoqao.com +taoquanquan.com taor.work taosaas.com taosdata.com @@ -87689,7 +88591,6 @@ tcnews.cc tcnvmms.com tcoejox.shop tcomall.com -tcp-crm.com tcp.hk tcp.mk tcp.pub @@ -87982,6 +88883,7 @@ tellmachgd.com telnet404.com telongzn.com telpo.com +telrgeam.cc telsda.com telu.net telunsu.net @@ -88463,7 +89365,6 @@ tgstech.com tgtest.site tgtianshanga.com tgzhz.com -th-music.com th-sjy.com th-tencentclb.cloud th-tencentclb.com @@ -88647,6 +89548,7 @@ threatbook.net threetong.com threewater.net thrive-chemicals.com +thrrip.space ths123.com ths8.com thsj.com @@ -89026,6 +89928,7 @@ tiexuedanxin.net tieyang.com tieyou.com tiezhima.com +tiezis.com tifenxi.com tiger-code.com tiger-info.com @@ -89079,6 +89982,7 @@ timeofdate.com timeoutbeijing.com timeoutcn.com timeoutshanghai.com +timephread.com timepill.net timeread.com timeread.net @@ -89114,6 +90018,7 @@ ting27.com ting55.com tingbook.com tingchewei.net +tingchucontrol.com tingclass.com tingfun.net tinghaohr.com @@ -89598,7 +90503,6 @@ to999.com toast.pub toaw.net tobaccochina.com -tobaccodb.org tobaccosz.com tobebetterjavaer.com toberp.com @@ -89607,6 +90511,7 @@ tobnews.com tobo1688.com tobosu.com tobsnssdk.com +tochgo.com tochinajob.com tocomtech.com tocosc.com @@ -89652,6 +90557,7 @@ tohosting.com tohosting.net tohours.com toidea.com +toiue.com tokcoder.com tokeimall080.com token-ad.com @@ -89659,7 +90565,6 @@ token-sensor.com tokenglish.com tokensky.net tokimekiclub.org -tol.vip tol24.com tom.cat tom.com @@ -90237,6 +91142,7 @@ transfriend.com transfu.com transfun.net translatego.net +translatellm.asia translationchina.com transmission-china.org transmonkey.com @@ -90387,7 +91293,6 @@ trust-one.com trust400.com trusta-lawyer.com trustasia.com -trustasia.net trustcn.com trustdecision.com trustedsign.com @@ -90600,7 +91505,6 @@ ttn8.com ttnea.com ttnongli.com ttnqh.com -ttoffice.net ttongda.com ttpaicdn.com ttpaihang.com @@ -90713,6 +91617,7 @@ tubiaoxiu.com tubie.art tuboshu.com tuboshuxinxi.xyz +tuboxu.com tubutu.com tuchong.com tuchong.net @@ -90882,6 +91787,7 @@ tupian114.com tupian1234.com tupian365.com tupiancunchu.com +tupiankucdn.com tupianyun.com tupianzj.com tupu360.com @@ -90932,9 +91838,11 @@ tusstar.com tusu.cc tusuguanjiage.com tusujiu.com +tutdown.com tutechanhb.com tutengcloud.com tutengdai.com +tutu-caonima.com tutuapp.com tutucloud.com tutudati.com @@ -91225,7 +92133,6 @@ typhoon.vip typicalchn.com typora.net typrotech.com -typstify.com tyqian.com tyqxzx.com tyrantg.com @@ -91329,7 +92236,6 @@ tztusi.com tzujian.com tzvec.com tzvtc.com -tzwdsoft.com tzwpco.com tzxingtang.com tzxwzx.com @@ -91479,9 +92385,6 @@ uccastore.com uccbikes.com uccpd.com ucdchina.com -ucdns22.com -ucdns4.com -ucdns7.com ucdok.com ucdrs.net ucfly.com @@ -91720,6 +92623,7 @@ ukh5.com ukiea.com ukin-tech.com ukingtech.com +uklga.work ukluhu.com ukongm.com ukonline2000.com @@ -91757,6 +92661,7 @@ ulikestatic.com ulinix.com ulinix.tv ulinkcollege.com +ulixirglobal.com ulpay.com ultracachenode.com ultraeda.com @@ -91895,7 +92800,6 @@ uniostay.com uniow.com unipack-tech.com uniproud.com -unisapps.com uniscope.com unisdigital.com unisedu.com @@ -91951,6 +92855,7 @@ unlgroup.com unlimax.com unlimithealth.com unlitui.com +unmou.com unn114.com unncahs.net unnnnn.com @@ -91963,6 +92868,7 @@ unsedu.com unsemi.com.tw unshan.com unsuv.com +untbbsdwarfs.com unvmax.com unwtonews.com unyx.com @@ -92061,6 +92967,7 @@ upsapp.com upseller.com upskyshanghai.com upst86.com +upsunet.com uptom.com uptougu.com upu-opt.com @@ -92115,6 +93022,7 @@ urlos.com urlsecajog.com uroandrologyseries.com urocancer.org +urongda.com urovo.com urovoworld.com urq7wvyumzfdi5.com @@ -92127,6 +93035,7 @@ urumqimarathon.com urumqimtr.com us-ex.com us-qianzheng.com +us.ci us0.me usa-gam.com usa-idc.com @@ -92333,6 +93242,7 @@ uwenku.com uwn.com uwntek.com uworter.com +uwparking.com ux18.com uxacn.com uxiangzu.com @@ -92756,6 +93666,7 @@ vgvmotor.com vgyuhu.com vhall.com vhallyun.com +vhao123.com vhengdata.com vhgu5ivtew.com vhiphop.com @@ -92801,6 +93712,7 @@ victory-inflight.com victorybio.com viczz.com vid12060hl.com +vida-bio.com viday.org videaba.com videasoft.com @@ -92869,6 +93781,7 @@ vinchin.com vindapaper.com vinehoo.com vinetreetents.com +vingoojuice.com vinjn.com vinkdong.com vinlion.com @@ -93038,6 +93951,7 @@ vitagou.hk vitalgen.com vitaloffice.life vitalxyz.com +vitarn.com vitasoy-chn.com vitechliu.com viterbi-tech.com @@ -93334,6 +94248,7 @@ vpea.ca vpgame.com vpgamecdn.com vpiaotong.com +vpie.net vpimg1.com vpimg2.com vpimg3.com @@ -93369,7 +94284,6 @@ vpspurchase.com vpsrb.com vpsss.net vpssw.com -vpsvs.com vpsvsvps.com vpszh.com vptek.com @@ -93454,7 +94368,6 @@ vsyy.net vt-pharm.com vtache.com vtcsy.com -vtdd.vip vteamgroup.com vtears.com vtepai.com @@ -93491,6 +94404,7 @@ vutimes.com vuz.me vv-tool.com vv.cc +vv1628.cc vv27.com vv3111.com vv8.com @@ -93586,6 +94500,7 @@ w3ccoo.com w3cfba6ufr.com w3chtml.com w3cjava.com +w3cplus.com w3cschool.cc w3ctalk.com w3ctech.com @@ -93708,6 +94623,8 @@ walkingpad.com walkkind.com walknode.com walkthechat.com +wallcoo.com +wallcoo.net walle-web.io wallet.io walletio.io @@ -93829,6 +94746,7 @@ wangdai555.com wangdaibdt.com wangdaicaifu.com wangdaiguancha.com +wangdaisj.com wangdali.net wangdaodao.com wangdianchaxun.com @@ -93857,6 +94775,7 @@ wangjiezhe.com wangjm.com wangjubao.com wangjumeng.xin +wangjun.art wangjunwei.com wangkangzhong.com wangkewang.com @@ -93889,6 +94808,7 @@ wangshu.la wangshuashua.com wangshugu.com wangsu.com +wangsu.net wangsucloud.com wangsuedge.com wangsuedge.net @@ -93948,7 +94868,6 @@ wangzhuanz.com wanh5.com wanhea.com wanhebin.com -wanhengtech.com wanheweb.com wanhi.com wanhongbao.com @@ -94298,6 +95217,7 @@ wdace.com wdad.cc wdashi.com wdaveh5game.com +wdazgscbxh2.com wddcn.com wddns.net wddream.com @@ -94430,6 +95350,7 @@ webgzs.com webhek.com webiolotech.com webjike.com +webjx.com webkaka.com webkdcdn.com webkf.net @@ -94458,6 +95379,7 @@ website.xin websitecname.com websjcdn.com websjy.com +websky.biz websocket-test.com websoso.com websztz.com @@ -94564,7 +95486,6 @@ weibanan.com weibang.vip weibangong.com weibanzhushou.com -weibo weibo.com weibo.tv weibocdn.com @@ -94596,6 +95517,7 @@ weichuming.com weico.cc weico.com weicogif.com +weicon.cc weicot.com weicunmall.com weida888.com @@ -94781,7 +95703,6 @@ weishi100.com weishigz.com weishipin.com weishishuyuan.com -weishucdn.cloud weisiliang.com weismarts.com weistang.com @@ -94896,6 +95817,7 @@ weizhike.club weizhipin.com weizhishu.com weizhivet.com +weizhoudaoly.com weizhuangfu.com weizhuanji.com weizunkj.com @@ -95045,6 +95967,7 @@ wenkudao.com wenkudaquan.com wenkunet.com wenkuvip.com +wenkuwenku.com wenkuxiazai.net wenlc.com wenli-china.com @@ -95056,7 +95979,6 @@ wenmeng.com wenmi114.com wenmingban.com wenneart.com -wenpai.org wenpie.com wenqy.com wenroo.com @@ -95070,6 +95992,7 @@ wenshen.net wenshendaka.com wenshenxiu.com wenshuba.com +wenshubang.com wenshushu.com wensiluo.com wensli.com @@ -95493,6 +96416,7 @@ whidf.com whidy.net whiee.com whiie-expo.com +whimsywarpgame.cc whinfo.net whir.net whisperto.net @@ -96592,6 +97516,7 @@ worldnyjx.com worldpathclinic.com worldpowerliftingchina.com worldrobotconference.com +worlds-slotsimulator.com worldsteel.net worldwarner.com worldwayhk.com @@ -96599,6 +97524,7 @@ worldwu.com wort.cloud worthgarden.com worthtech.net +woruide.net worxperience.com wosai-inc.com wosaimg.com @@ -96680,7 +97606,6 @@ wpan123.com wpceo.com wpcio.com wpcsh.com -wpcy.com wpdaxue.com wpdian.com wpengapp.com @@ -96832,7 +97757,6 @@ wsimen.com wsipv6.com wsisp.com wsisp.net -wsjqnmmrmv.world wsjtxia.com wskam.com wskj16818.xyz @@ -97294,6 +98218,7 @@ wwstat.com www-666789.com www-76244.com www-jabuank-co-jp.net +www.adobe.com www.amd.com www.cdnetworks.com www.cg @@ -97304,6 +98229,7 @@ www.gov.mo www.htc.com www.nike.com www.redhat.com +www.samsung.com www.st.com www.vive.com www.viveport.com @@ -97312,6 +98238,7 @@ www100789.com www11ic-shinkni-ib-jp.com www2489.com www48-365365.com +www5929.com www9912.com wwwer.net wwwfkw.com @@ -97455,6 +98382,7 @@ wxmovie.com wxmuseum.com wxnacy.com wxngh.com +wxp2022.vip wxpangu.com wxpayui.com wxpmc.com @@ -97482,6 +98410,7 @@ wxshops.co wxshuku.la wxskysy.com wxslzf.com +wxsohu.com wxsswgs.com wxsteed.com wxstztg.com @@ -97789,6 +98718,7 @@ x7z.cc x81zw.co x81zw2.com x821.com +x86android.com x86pi.com x8ds.com x8sb.com @@ -97934,6 +98864,7 @@ xbase.cloud xbase.xyz xbatu.com xbauto.com +xbb8.com xbbaoan.com xbceo.com xbcjy.com @@ -98049,6 +98980,7 @@ xckpjs.com xckssw.com xckszx.com xclawyers.org +xcljs.com xcloudbase.com xcmad.com xcmg-dkrob.com @@ -98073,7 +99005,6 @@ xcrmyy.com xcsc.com xcstuido.com xcsyy.com -xcthings.com xctmr.com xcultur.com xcurrency.com @@ -98131,7 +99062,6 @@ xdiscuz.com xdj-sz.com xdja.com xdjcgs.com -xdjk.net xdju.com xdjunxiao.com xdjy369.com @@ -98343,6 +99273,7 @@ xgt2014.com xgtea.com xgxedu.com xgxsignage.com +xgyszj.com xgz.cc xgzbwdj.com xgzdhj.com @@ -98548,6 +99479,7 @@ xiangjiayaoyehz.com xiangkanwang.com xiangkesi.com xiangley.com +xiangliuzi.com xianglongpharm.com xianglute.com xiangmaita.com @@ -98567,6 +99499,7 @@ xiangqiyouxi.com xiangqu.com xiangrikui.com xiangrikuijianzhan.com +xiangrikuisite.com xiangruichina.com xiangruizulin.com xiangshang360.com @@ -98771,7 +99704,6 @@ xiaoe-materials.com xiaoe-tech.com xiaoe-tools.com xiaoecloud.com -xiaoedata.com xiaoeeye.com xiaoeknow.com xiaoenai.com @@ -98809,7 +99741,6 @@ xiaohe-jiankang.com xiaohe666.com xiaoheihegame.com xiaoheima.com -xiaoheiribao.com xiaohelive.com xiaohengmaidan.com xiaohi.cc @@ -99285,7 +100216,6 @@ xihaxueche.com xihazsww.com xihegp.com xiherencai.com -xihuan xihuan.me xihusgh.com xiimoon.com @@ -99423,7 +100353,6 @@ xindns.com xindong.com xindongdinglv.com xindonghuyu123.com -xindonshan.com xindu.cc xinduo.com xinduoad.com @@ -99490,7 +100419,6 @@ xinghaoyun8.com xinghejoy.com xinghengedu.com xingheoa.com -xinghewanglu.com xinghuaport.com xinghuazixun.com xinghuo100.com @@ -99508,6 +100436,7 @@ xingjun-group.com xingkec.com xingkeqi.com xingketech.com +xingkong.link xingkongfy.xyz xingkongmt.com xingkupai.com @@ -99556,6 +100485,7 @@ xingtaishipping.com xingtan.one xingtan001.com xingtangzp.com +xingtongsw.com xingtu.com xingtui520.com xinguad.com @@ -99667,6 +100597,7 @@ xiniuz.com xinjiadiy.com xinjianggames.com xinjianggou.com +xinjiangroup.com xinjiashangtou.com xinjidian.com xinjifangchan.com @@ -99772,7 +100703,6 @@ xinruipiao.com xinruiweb.com xinsanbanbao.com xinsankeji.com -xinsannong.com xinsdn.com xinsenz.com xinsf.cc @@ -99783,6 +100713,7 @@ xinshengku.com xinshengsemi.com xinshi525.com xinshiba.com +xinshipu.com xinshishen.com xinshouyou.com xinshouzhanzhang.com @@ -99907,6 +100838,7 @@ xinyustone.com xinyutengyuan.com xinzegongshui.com xinzengwj.net +xinzheng.cc xinzhi.com xinzhi.space xinzhibang168.com @@ -99988,6 +100920,7 @@ xitong-tech.com xitong114.com xitong5.com xitong8.com +xitong86.com xitongcity.com xitongdaquan.net xitonggho.com @@ -100206,6 +101139,7 @@ xjnnet.net xjnzm.com xjoycity.com xjpdf.com +xjphsd.com xjpnmt.com xjqixing.com xjqysw.com @@ -100342,7 +101276,7 @@ xlongm.com xloveyoux.com xlpai.com xlpan.com -xlps.site +xlpp.net xlqeai.com xlqzh.com xlread.com @@ -100557,7 +101491,9 @@ xn--55qw7biqf7g3d004h8yuw1ctrt04sep1cjfb.com xn--55qx2ag79c1iq.com xn--55qx5d xn--55qzsxj5y4ozkz93dswptmau4jc88dg75ctid8raf29c.com +xn--5brz4b846h.com xn--5kv317c.com +xn--5kv91jiz2b.com xn--5tzm5g xn--6fr61zj8c92fg34d.com xn--6frz82g @@ -100579,7 +101515,7 @@ xn--8y0a063a xn--91-y60d638c.xyz xn--9et52u xn--9kr72kqwe.com -xn--9krt00a +xn--9krv3x413bbyb.com xn--9myo55bi8l.com xn--9pr56vfna007k.com xn--9pry86bq5ex68c.org @@ -100597,10 +101533,12 @@ xn--cjztj18l.com xn--cpqr0dg9g4t0fodaq5c.com xn--cssw8z54rj2ds2q.com xn--czr694b +xn--czr93rxry.com xn--czrs0t xn--czru2d xn--czru2dx3eszw3lat53b.com xn--dev407h.com +xn--djr48g6sik7q.com xn--djr58xb1mzxk3mtrhfc8g.com xn--dkr447bejn.com xn--dkr447bejn.net @@ -100614,6 +101552,7 @@ xn--fhq79jyym9nh74hfm8a.com xn--fiq03fftg7m2c.com xn--fiq06jqoz14s.com xn--fiq228c5hs +xn--fiq446amrnx0i.com xn--fiq60luzcp97a7oxivr99k.org xn--fiq64b xn--fiq6q20pz51d.com @@ -100662,6 +101601,7 @@ xn--ntso8dsz1borbzvfm5h.com xn--ntsp37j.net xn--nyqx2gbsm8u0b.com xn--nyqy26a +xn--ohqn1dw64cf45c8l9a1ba.com xn--omrvgz6er31au6f.com xn--otu796d xn--p5t28ylet56a.com @@ -100685,6 +101625,7 @@ xn--ruqz9zcojm5sf19a.com xn--s4t325g.com xn--ses554g xn--sgt856gbjl.cc +xn--siq0gv77a3c.com xn--sosw2ge0bs10aoq0a.com xn--sss604efuw.com xn--swts8irvtrtr.com @@ -100843,7 +101784,6 @@ xqbase.com xqblog.com xqbssj.com xqce.com -xqdaolove.com xqdgroup.com xqdjkwz.com xqfunds.com @@ -100983,6 +101923,7 @@ xsrm.com xsrtvu.com xss.tv xssdcdn.com +xsser.date xsshuku.com xsslyjt.com xssz.net @@ -101008,6 +101949,7 @@ xsyx.xyz xsyxsc.com xsyydtfy.com xsyzdl.com +xszhuan.com xszrcw.com xszww2.com xszww8.net @@ -101080,6 +102022,7 @@ xtzjup.com xtzpw.com xtzy.com xu1s.com +xu42x.net xu8.fun xuan5.com xuanba.com @@ -101172,6 +102115,7 @@ xuedi.com xuedingmiao.com xuedong97.com xuedou.com +xueepay.com xueersi.com xueersibook.com xueerxing.com @@ -101241,6 +102185,7 @@ xuetangx.com xuetengedu.com xuetimes.com xueto.com +xuetongw.com xuetu.net xueui.com xuewangshang.com @@ -101570,7 +102515,6 @@ xxkapp.com xxkucun.com xxkxjx.net xxkxw.net -xxlab.tech xxlcn.com xxlifexx.com xxliudsp.com @@ -101625,6 +102569,7 @@ xxxzzlm.org xxycw.com xxyeyan.com xxyfgy.com +xxyhzd.com xxykgl.com xxymdy.com xxymw.com @@ -101718,10 +102663,11 @@ xyhtml5.com xyhygs.com xyict.com xyimg.net -xyingsoft.com xyj.link xyj321.com +xyj618.com xyjbglass.com +xyjj99.com xyjs999.com xyjtzz.com xyjuyuan.com @@ -101798,6 +102744,7 @@ xywy.com xywyfw.com xywzs.com xyx234.com +xyx3d.com xyxcables.com xyxdie.com xyxrmt.com @@ -102082,6 +103029,7 @@ yahuhost.com yahui.cc yahui.hk yahunet.com +yahxxcl.com yaimg.com yaitest.com yaiyuan.com @@ -102302,6 +103250,7 @@ yanxianggroup.com yanxintong.com yanxishe.com yanxiu.com +yanxizhu.com yanxurui.cc yanyib2b.com yanyidakaoyan.com @@ -103038,6 +103987,7 @@ yfvb.com yfway.com yfwnm.com yfwpt.vip +yfwqlij.xyz yfycrc.com yfycyboil.com yfygxyy.com @@ -103069,7 +104019,6 @@ ygibao.com yginsight.com ygjctech.com ygjj.com -yglsr.com ygmsy.com ygread.com ygrtt.com @@ -103274,6 +104223,7 @@ yidaointernational.com yidaomall.com yidaomobi.com yidaplay.com +yidaweb.com yide.com yideamobile.com yidejia.com @@ -103306,6 +104256,7 @@ yidongtimes.com yidontek.com yidop.com yidoutang.com +yidouzhaofang.com yidu-marathon.com yidu.cc yidubbs.com @@ -103323,6 +104274,7 @@ yiec.com yiernews.com yiexi.com yifajingren.com +yifake.com yifan211.com yifancdn.com yifancn.com @@ -103813,6 +104765,7 @@ yiqihi.com yiqihuihui.com yiqijian.com yiqijixiang.com +yiqikan8.com yiqike.com yiqiming88.com yiqimob.com @@ -103972,6 +104925,7 @@ yixiaoyuan.com yixiaozu.com yixiatong.com yixiekeji.com +yixin-valve.com yixin.com yixin.im yixin5.com @@ -104053,6 +105007,7 @@ yizhedian.com yizhengwx.com yizhibi.com yizhibo.com +yizhidayu.com yizhifubj.com yizhijia.com yizhikan.com @@ -104308,6 +105263,7 @@ ylwpark.com ylws.net ylwyw.com ylxdtww.com +ylxgf.com ylxhmy.com ylxw.net ylxweb.com @@ -104788,6 +105744,7 @@ yougenet.com youginorg.com yougou.com yougu.tv +yougumuye.com youguo.com youguoquan.com youguu.com @@ -105011,6 +105968,7 @@ youxiaju.com youxiake.com youxiake.net youxiamotors.com +youxiangclub.com youxiaoad.com youxiaoge.com youxiaohou.com @@ -105076,6 +106034,7 @@ youyicun.net youyierp.com youyigame.com youyiguke.com +youyihuibk.com youyijiu.com youyiqi.com youyiqiaogou.com @@ -105352,6 +106311,7 @@ ysplay.com ysrencai.com ysrh.com ysrzdb.com +ysslc.com yssp88.com yssql.com yssrmyy.com @@ -105490,6 +106450,7 @@ ytyz.net ytyz.org ytzhihui.com ytzq.com +ytzww.com yu-electronics.net yu163.com yu4l.com @@ -105887,7 +106848,6 @@ yumingguwen.com yumingyouhui.com yummy.tech yumstone.com -yun yun-ac.com yun-dns.com yun-gu.com @@ -106013,6 +106973,7 @@ yungongchang.com yungotec.com yungou618.com yungouos.com +yungousj.net yungpu.com yungu.org yunguajibao.com @@ -106291,7 +107252,6 @@ yuqianshu.com yuqiao.com yuqiaolong.com yuqinpin.com -yuqncloud.com yuquanhosp.com yuque.com yur09130og.com @@ -106928,6 +107888,7 @@ z-henergy.com z-inn.com z-tek.com z.ai +z.wiki z0.cc z01.com z0ukun.com @@ -106949,6 +107910,7 @@ z5w.net z668.net z6e.com z701.com +z729.com z7xz.com z888.net z8q.cc @@ -106960,8 +107922,6 @@ zabxib.com zac1993.com zachina.org zack.asia -zaconad.com -zaconbs.com zadervice.com zaduonews.com zaecu.com @@ -107068,6 +108028,7 @@ zaojiaxueshe.com zaojiu.com zaojuge.com zaojuzi.com +zaojv.com zaoowoo.com zaowandushu.com zaowuyun.com @@ -107248,6 +108209,7 @@ zckp.com zclkj.com zclock.xyz zcloud.world +zclqzls.com zcmlc.com zcmol.com zcmpark.com @@ -107272,6 +108234,7 @@ zcrcw.com zcrczp.com zcread.com zcry007.com +zcs.cc zcsbbs.com zcscz.com zcset.com @@ -107630,7 +108593,6 @@ zggtxhw.com zggua.com zggwy.com zggwy.org -zggx100.com zggxgp.com zggxhj.com zggxlbc.com @@ -107871,12 +108833,14 @@ zh-cjh.com zh-gs.com zh-hbs.com zh-heshi.com +zh-hks-slotsimulator.com zh-hr.com zh-hz.com zh-itone.com zh-jieli.com zh-jinhang.com zh-languan.com +zh-le-slotsimulator.com zh-longshi.com zh-piao.com zh10.com @@ -108106,8 +109070,10 @@ zhaoshizhou.xyz zhaosifang.com zhaosuliao.com zhaosw.com +zhaosy.com zhaota8.com zhaotaicaiyin.com +zhaotu.com zhaouc.com zhaouc.net zhaowenyu.com @@ -108279,6 +109245,7 @@ zhengzhenxx.com zhengzhou42195.com zhengzhoubus.com zhengzhoutools.com +zhengzhoutx.com zhengzhouyunmei.com zhengzihui.com zhenhaofu.com @@ -108287,6 +109254,7 @@ zhenhaotv.com zhenheli.com zhenhonggroup.com zhenhospital.com +zhenhuadj.com zhenhuan888.com zhenimg.com zhenjiatong.com @@ -108335,6 +109303,7 @@ zheurl.com zhewanji.net zhexi.tech zhexingzx.com +zhexinit.com zheye.com zheyibu.com zheyinleasing.com @@ -108472,7 +109441,6 @@ zhihuichuangyanshi.com zhihuiep.com zhihuifangdong.net zhihuihongze.com -zhihuihub.com zhihuihuiwu.com zhihuihutong.com zhihuiji.com @@ -108625,6 +109593,7 @@ zhito.com zhitongcaijing.com zhitongpm.com zhitouxing.com +zhituad.com zhitui.com zhituwujie.com zhituzhilian.com @@ -109006,6 +109975,7 @@ zhongzhifaqian.com zhongzhiiov.com zhongzhilin.com zhongzhixin.com +zhongzhixitong.com zhongzhongkeji.com zhongzhoulianhe.com zhongzhouwater.com @@ -109173,6 +110143,7 @@ zhuimabk.com zhuimeng8.com zhuimengzhu.com zhuinianqing.com +zhuinw.com zhuiqu.com zhuishu.la zhuishukan.com @@ -109806,6 +110777,7 @@ zjgj.com zjgjj.com zjgkg.com zjgmwl.com +zjgqt.org zjgrc.com zjgroupedu.com zjgrrb.com @@ -110297,6 +111269,7 @@ zkscgj.com zksglaser.com zkshare.com zksn.com +zksoftwaresz.com zksps.com zksyzy.com zktecn.com @@ -110327,6 +111300,7 @@ zkzbkj.com zkzj.org zkzls.com zkzn666.com +zkzs.net zl-steelpipe.com zl1006.com zl56.com @@ -110385,6 +111359,7 @@ zls365.com zlscn.net zlsin.com zlsoft.com +zlsqlt.com zlsss.com zlsyun.com zlt365.com @@ -110506,6 +111481,7 @@ znjs.com znjsjt.com znjtgf.com znjttz.com +znlc2015.com znlcn.org znlerp.com znlh.com @@ -111101,7 +112077,6 @@ zuiyouxi.com zuizhifu.com zuji360.com zujiant.com -zujingpai.com zujuan.com zujuanku.com zujuanyi.com @@ -111280,6 +112255,7 @@ zx100.com zx10000.com zx110.org zx1234.com +zx16f.com zx181.com zx18x.com zx350zx.com @@ -111346,6 +112322,7 @@ zxneweye.com zxnic.net zxnrh.com zxoid.com +zxopen.com zxpcloud.com zxpec.com zxpmq.com @@ -111356,6 +112333,7 @@ zxsauto.com zxsctf.com zxshe.com zxsmd.com +zxsou.com zxstyl.com zxswjx.com zxsx.org @@ -111752,581 +112730,3 @@ zzzyy.com zzzyyy.com zzzzaaaa.com zzzzzz.me -2mdn-cn.net -2mdn.net -a1.mzstatic.com -a2.mzstatic.com -a3.mzstatic.com -a4.mzstatic.com -a5.mzstatic.com -account-cn.alibabacloud.com -activate.activation-v2.kaspersky.com -activation-v2.geo.kaspersky.com -activation-v2.kaspersky.com -adcdownload.apple.com -adcdownload.apple.com.akadns.net -admob-cn.com -adservice.google.com -afcs.dell.com -ai.zhaomi.cn -alibaba.cdn.steampipe.steamcontent.com -amp-api-edge-lb-cn.itunes-apple.com.akadns.net -amp-api-edge-lb.itunes-apple.com.akadns.net -amp-api-edge.apps.apple.com -amp-api-edge.music.apple.com -amp-api-search-edge.apps.apple.com -amp-api-updates.apps.apple.com -amp-api.apps.apple.com -amp-api.media.apple.com -amp-api.music.apple.com -aod-ssl.itunes.apple.com -aod.itunes.apple.com -api-edge.apps.apple.com -api.alibabacloud.com -app-analytics-services.com -app-measurement-cn.com -app-measurement.com -app-site-association.cdn-apple.com -appldnld.apple.com -appldnld.g.aaplimg.com -appleid.cdn-apple.com -apps.mzstatic.com -apps5.oingo.com -apptrailers.itunes.apple.com -auth.music.apple.com -avail.googleflights.net -azuremigrate.download.prss.microsoft.com -azuremigratetest.download.prss.microsoft.com -azurestackhub.download.prss.microsoft.com -azurestackhubuat.download.prss.microsoft.com -b.c2r.ts.cdn.office.net -bag-cdn.itunes-apple.com.akadns.net -bag.itunes.apple.com -beacons.gcp.gvt2.com -beacons.gvt2.com -beacons2.gvt2.com -beacons3.gvt2.com -bj1.api.bing.com -bookkeeper.itunes.apple.com -c.admob.com -c.android.clients.google.com -c.pki.goog -cache-management-prod.google.com -cache.pack.google.com -cbdstest.download.prss.microsoft.com -cdn-cn.apple-mapkit.com -cdn-cn1.apple-mapkit.com -cdn-cn2.apple-mapkit.com -cdn-cn3.apple-mapkit.com -cdn-cn4.apple-mapkit.com -cdn.ampproject.org -cdn.apple-mapkit.com -cdn.globalsigncdn.com.cdn.cloudflare.net -cdn1.apple-mapkit.com -cdn2.apple-mapkit.com -cdn3.apple-mapkit.com -cdn4.apple-mapkit.com -cds-cdn.v.aaplimg.com -cds.apple.com -cds.apple.com.akadns.net -cdsassets.apple.com -certs-lb.apple.com.akadns.net -certs.apple.com -checkin.gstatic.com -cl1-cdn.origin-apple.com.akadns.net -cl1.apple.com -cl2-cdn.origin-apple.com.akadns.net -cl2-cn.apple.com -cl2.apple.com -cl2.apple.com.edgekey.net.globalredir.akadns.net -cl3-cdn.origin-apple.com.akadns.net -cl3.apple.com -cl4-cdn.origin-apple.com.akadns.net -cl4-cn.apple.com -cl4.apple.com -cl5-cdn.origin-apple.com.akadns.net -cl5.apple.com -clickserve.cc-dt.com -clickserve.dartsearch.net -clickserver.googleads.com -client-api.itunes.apple.com -clientflow.apple.com -clientflow.apple.com.akadns.net -clientperipherals.dell.com -clientservices.googleapis.com -cma.itunes.apple.com -cn-smp-paymentservices.apple.com -cn.alibabacloud.com -cn.download.nvidia.com -cn.widevine.com -cn.windowssearch.com -collaborate.download.prss.microsoft.com -collaborateppe.download.prss.microsoft.com -communities.apple.com -configuration.apple.com -configuration.apple.com.akadns.net -connectivitycheck.gstatic.com -crashlyticsreports-pa.googleapis.com -crl-lb.apple.com.akadns.net -crl.apple.com -crl.globalsign.net -crl.kaspersky.com -crl.pki.goog -crls.pki.goog -csi.gstatic.com -cstat.apple.com -cstat.cdn-apple.com -ctdsb.clouddiffuse.xyz -ctldl.windowsupdate.com -cueme-api.quark.cn -customization-cdn.dell.com -dartsearch-cn.net -dc1-cert.ksn.kaspersky-labs.com -dc1-file.ksn.kaspersky-labs.com -dc1-pp.ksn.kaspersky-labs.com -dc1-st.ksn.kaspersky-labs.com -dc1.ksn.kaspersky-labs.com -dd-cdn.origin-apple.com.akadns.net -dds.dell.com -dejavu.apple.com -developer.microsoft.com -devimages-cdn.apple.com -devstreaming-cdn.apple.com -dg-meta.video.google.com -discussionschinese.apple.com -dl.dell.com -dl.google.com -dl.l.google.com -dnl-00.geo.kaspersky.com -dnl-01.geo.kaspersky.com -dnl-02.geo.kaspersky.com -dnl-03.geo.kaspersky.com -dnl-04.geo.kaspersky.com -dnl-05.geo.kaspersky.com -dnl-06.geo.kaspersky.com -dnl-07.geo.kaspersky.com -dnl-08.geo.kaspersky.com -dnl-09.geo.kaspersky.com -dnl-10.geo.kaspersky.com -dnl-11.geo.kaspersky.com -dnl-12.geo.kaspersky.com -dnl-13.geo.kaspersky.com -dnl-14.geo.kaspersky.com -dnl-15.geo.kaspersky.com -dnl-16.geo.kaspersky.com -dnl-17.geo.kaspersky.com -dnl-18.geo.kaspersky.com -dnl-19.geo.kaspersky.com -doubleclick-cn.net -doubleclick.net -download.developer.apple.com -download.microsoft.com -download.mlcc.google.com -download.tensorflow.google.com -download.visualstudio.microsoft.com -downloaddispatch.itunes.apple.com -dreamspark.download.prss.microsoft.com -dreamsparkuat.download.prss.microsoft.com -ea2cn-dev-outlet.dell.com -ea2cn-prod-outlet.dell.com -ea2cn-staging-outlet.dell.com -edge.geo.kaspersky.com -emoi-cncdn.bing.com -end-api.shallow.ink -end.shallow.ink -entitlements-edge.itunes.apple.com -experiments.apple.com -f.c2r.ts.cdn.office.net -fcs.dell.com -fides-pol.apple.com -firebase-settings.crashlytics.com -fontfiles.googleapis.com -fonts.googleapis.com -fonts.gstatic.com -fpinit.itunes.apple.com -fta.dell.com -ftaapj.dell.com -ftaemea.dell.com -ftasitapj.dell.com -g0.gstatic.com -g1.gstatic.com -g2.gstatic.com -g3.gstatic.com -gbxgateway-dev.dell.com -gbxgateway.dell.com -geetest.datasink.sensorsdata.cn -gog.qtlglb.com -gonglchuangl.net -gongyichuangyi.net -google-analytics-cn.com -google-analytics.com -googleadservices-cn.com -googleadservices.com -googleanalytics.com -googleapis-cn.com -googleapps-cn.com -googleflights-cn.net -googleoptimize-cn.com -googleoptimize.com -googlesyndication-cn.com -googlesyndication.com -googletagmanager-cn.com -googletagmanager.com -googletagservices-cn.com -googletagservices.com -googletraveladservices-cn.com -googletraveladservices.com -googlevads-cn.com -gs-loc-cn.apple.com -gs-loc.apple.com -gsp10-ssl-cn.ls.apple.com -gsp11-cn.ls.apple.com -gsp12-cn.ls.apple.com -gsp13-cn.ls.apple.com -gsp4-cn.ls.apple.com -gsp4-cn.ls.apple.com.edgekey.net.globalredir.akadns.net -gsp5-cn.ls.apple.com -gsp85-cn-ssl.ls.apple.com -gspe11-2-cn-ssl.ls.apple.com -gspe12-cn-ssl.ls.apple.com -gspe19-2-cn-ssl.ls-apple.com.akadns.net -gspe19-2-cn-ssl.ls.apple.com -gspe19-cn-ssl.ls.apple.com -gspe19-cn.ls-apple.com.akadns.net -gspe19-cn.ls.apple.com -gspe21-ssl.ls.apple.com -gspe35-ssl.ls.apple.com -gspe79-cn-ssl.ls.apple.com -gspe85-cn-ssl.ls.apple.com -gstatic-cn.com -gstaticadssl.l.google.com -gtm.oasisfeng.com -guzzoni-apple-com.v.aaplimg.com -guzzoni.apple.com -guzzoni.smoot.apple.com -hellofontpreview.oss-cn-beijing.aliyuncs.com -hellowebfonts.oss-cn-beijing.aliyuncs.com -hospital.pku.edu.cn -i.dell.com -i.pki.goog -images-cn-8.ssl-images-amazon.com -images-cn.ssl-images-amazon.com -images.apple.com.edgekey.net.globalredir.akadns.net -imasdk.googleapis.com -inappcheck-cn.itunes-apple.com.akadns.net -inappcheck-lb.itunes-apple.com.akadns.net -inappcheck.itunes.apple.com -init-kt.apple.com -init-p01md-lb.push-apple.com.akadns.net -init-p01md.apple.com -init-p01st-lb.push-apple.com.akadns.net -init-p01st.push.apple.com -init-s01st-lb.push-apple.com.akadns.net -init-s01st.push.apple.com -init.ess.apple.com -init.gc-lb.apple.com.akadns.net -init.gc.apple.com -init.itunes.apple.com -iosapps.itunes.apple.com -iosapps.itunes.g.aaplimg.com -ipcdn.apple.com -iphone-ld.apple.com -iphone-ld.origin-apple.com.akadns.net -is-ssl.mzstatic.com-cn-lb.itunes-apple.com.akadns.net -is1-ssl.mzstatic.com -is1.mzstatic.com -is2-ssl.mzstatic.com -is2.mzstatic.com -is3-ssl.mzstatic.com -is3.mzstatic.com -is4-ssl.mzstatic.com -is4.mzstatic.com -is5-ssl.mzstatic.com -is5.mzstatic.com -itacademy.download.prss.microsoft.com -itacademyuat.download.prss.microsoft.com -itunes-apple.com.akadns.net -itunes.apple.com -itunesconnect.apple.com -izattime.qcomgeo2.com -js-cdn.music.apple.com -km.support.apple.com -ksn-cinfo-geo.kaspersky-labs.com -ksn-cinfo.geoksn.kaspersky.com -ksn-crypto-info-geo.kaspersky-labs.com -ksn-crypto-kas-geo.kaspersky-labs.com -ksn-crypto-tcert-geo.kaspersky-labs.com -ksn-crypto-url-geo.kaspersky-labs.com -ksn-crypto-verdict-geo.kaspersky-labs.com -ksn-crypto-wifiplus-geo.kaspersky-labs.com -ksn-crypto-wifiplus.geoksn.kaspersky.com -ksn-dc1-cert.geoksn.kaspersky.com -ksn-dc1-file.geoksn.kaspersky.com -ksn-dc1.geoksn.kaspersky.com -ksn-file-geo.kaspersky-labs.com -ksn-file.geoksn.kaspersky.com -ksn-info-geo.kaspersky-labs.com -ksn-info.geoksn.kaspersky.com -ksn-kas.geoksn.kaspersky.com -ksn-mdr.geoksn.kaspersky.com -ksn-tcert-geo.kaspersky-labs.com -ksn-tcert.geoksn.kaspersky.com -ksn-url-geo.kaspersky-labs.com -ksn-url.geoksn.kaspersky.com -ksn-verdict-geo.kaspersky-labs.com -ksn-verdict.geoksn.kaspersky.com -lex.download.prss.microsoft.com -lexuat.download.prss.microsoft.com -lv.queniujq.cn -maps.apple.com -mbs.download.prss.microsoft.com -mdr.ksn.kaspersky-labs.com -mensura.cdn-apple.com -mesu-cdn.apple.com.akadns.net -mesu-china.apple.com.akadns.net -mesu.apple.com -misc-assets.itunes.apple.com -ml.cdn-apple.com -moocs.unipus.cn -mpnbenefits.download.prss.microsoft.com -mpnbenefitsrtl.download.prss.microsoft.com -mpnbenefitsrtluat.download.prss.microsoft.com -msdn.download.prss.microsoft.com -msdprod-ad.download.prss.microsoft.com -msproduct.download.prss.microsoft.com -music.apple.com -musicstatus.music.apple.com -mvod.itunes.apple.com -myapp.itunes.apple.com -myvs.download.prss.microsoft.com -nexus.dell.com -np-edge.itunes.apple.com -ntp.aliyun.com -ntp.tencent.com -ntp1.aliyun.com -ntp1.tencent.com -ntp2.aliyun.com -ntp2.tencent.com -ntp3.aliyun.com -ntp3.tencent.com -ntp4.aliyun.com -ntp4.tencent.com -ntp5.aliyun.com -ntp5.tencent.com -ntp6.aliyun.com -ntp7.aliyun.com -o.pki.goog -ocsp.apple.com -ocsp.pki.goog -ocsp2.apple.com -oemsoc.download.prss.microsoft.com -oemsocuat.download.prss.microsoft.com -officecdn.microsoft.com -officemkt.download.prss.microsoft.com -officemktuat.download.prss.microsoft.com -opencourse.pku.edu.cn -oscdn.apple.com -oscdn.origin-apple.com.akadns.net -osrelease.download.prss.microsoft.com -osxapps.itunes.apple.com -osxapps.itunes.g.aaplimg.com -p.cdn.persaas.dell.com -p00.upd.kaspersky.com -p01.upd.kaspersky.com -p02.upd.kaspersky.com -p03.upd.kaspersky.com -p04.upd.kaspersky.com -p05.upd.kaspersky.com -p06.upd.kaspersky.com -p07.upd.kaspersky.com -p08.upd.kaspersky.com -p09.upd.kaspersky.com -p1-juejin.byteimg.com -p10.upd.kaspersky.com -p11.upd.kaspersky.com -p12.upd.kaspersky.com -p13.upd.kaspersky.com -p14.upd.kaspersky.com -p15.upd.kaspersky.com -p16.upd.kaspersky.com -p17.upd.kaspersky.com -p18.upd.kaspersky.com -p19.upd.kaspersky.com -p2-juejin.byteimg.com -p3-juejin.byteimg.com -p3-novel.byteimg.com -p4-juejin.byteimg.com -p5-juejin.byteimg.com -p6-juejin.byteimg.com -p6-novel.byteimg.com -p7-juejin.byteimg.com -p8-juejin.byteimg.com -p9-juejin.byteimg.com -pagead-googlehosted.l.google.com -pancake.apple.com -pba0.apple.com -pd-nk.itunes.apple.com -pd.itunes.apple.com -performanceparameters.googleapis.com -pki-goog.l.google.com -play.itunes.apple.com -play.music.apple.com -probe.siri.apple.com -prod-controlbe.floonet.goog -prod-databe.floonet.goog -prod-support.apple-support.akadns.net -prod.databe.floonet.goog -publicassets.cdn-apple.com -qiao-cn.com -qpx.googleflights.net -recaptcha-cn.net -recaptcha.net -redirector.bdn.dev -redirector.c.chat.google.com -redirector.c.mail.google.com -redirector.c.pack.google.com -redirector.c.play.google.com -redirector.c.youtubeeducation.com -redirector.gcpcdn.gvt1.com -redirector.gvt1.com -redirector.offline-maps.gvt1.com -redirector.snap.gvt1.com -redirector.xn--ngstr-lra8j.com -refresh-bkg.activation-v2.kaspersky.com -rsm.download.prss.microsoft.com -s.mzstatic.com -s00.upd.kaspersky.com -s01.upd.kaspersky.com -s02.upd.kaspersky.com -s03.upd.kaspersky.com -s04.upd.kaspersky.com -s05.upd.kaspersky.com -s06.upd.kaspersky.com -s07.upd.kaspersky.com -s08.upd.kaspersky.com -s09.upd.kaspersky.com -s1.mzstatic.com -s10.upd.kaspersky.com -s11.upd.kaspersky.com -s12.upd.kaspersky.com -s13.upd.kaspersky.com -s14.upd.kaspersky.com -s15.upd.kaspersky.com -s16.upd.kaspersky.com -s17.upd.kaspersky.com -s18.upd.kaspersky.com -s19.upd.kaspersky.com -s2.mzstatic.com -s3.mzstatic.com -s4.mzstatic.com -s5.mzstatic.com -safebrowsing-cache.google.com -safebrowsing.googleapis.com -scene7-cdn.dell.com -sdx.microsoft.com -se-edge.itunes.apple.com -se2.itunes.apple.com -search.itunes.apple.com -seed-sequoia.siri.apple.com -seed-swallow.siri.apple.com -seed.siri.apple.com -sequoia.apple.com -service.urchin.com -sf-api-token-service.itunes.apple.com -sh-pod2-smp-device.apple.com -shazam-insights.cdn-apple.com -shell.cdn.office.net -si.cdn.dell.com -sm.dell.com -smp-device-content.apple.com -snp.cdn.dell.com -snpi.dell.com -software.download.prss.microsoft.com -sp.itunes.apple.com -speedysub.music.apple.com -ss.bjmu.edu.cn -ssl-google-analytics.l.google.com -ssl.gstatic.com -staging-controlbe.floonet.goog -staging-databe.floonet.goog -staging.databe.floonet.goog -static.gc.apple.com -statics.teams.cdn.office.net -status.alibabacloud.com -stocks-sparkline-lb.apple.com.akadns.net -stocks-sparkline.apple.com -store.apple.com -store.apple.com.edgekey.net -store.apple.com.edgekey.net.globalredir.akadns.net -store.storeimages.apple.com.akadns.net -store.storeimages.cdn-apple.com -storecorefulfillment.download.prss.microsoft.com -storeedge.microsoft.com -storeedgefd.dsx.mp.microsoft.com -streamingaudio.itunes.apple.com -study.163.com -su.itunes.apple.com -support-china.apple-support.akadns.net -support.apple.com -supportassist.dell.com -surface.download.prss.microsoft.com -surface.downloads.prss.microsoft.com -swallow-apple-com.v.aaplimg.com -swallow.apple.com -swcatalog-cdn.apple.com.akadns.net -swcatalog.apple.com -swcdn.apple.com -swcdn.g.aaplimg.com -swdist.apple.com -swdist.apple.com.akadns.net -swscan-cdn.apple.com.akadns.net -swscan.apple.com -sylvan.apple.com -sync.itunes.apple.com -tac.googleapis.com -tesla-cdn.thron.cn -tf-feedback.itunes.apple.com -time.amazonaws.cn -time.izatcloud.net -time.xtracloud.net -tj-pod1-smp-device.apple.com -tools.google.com -tp.louisvuitton.com -universal-activity-service.itunes.apple.com -update.crashlytics.com -update.googleapis.com -updates-http.cdn-apple.com -updates-http.cdn-apple.com.akadns.net -updates.cdn-apple.com -uplaypc-s-ubisoft.cdn.ubi.com -upp.itunes.apple.com -valid.apple.com -valid.origin-apple.com.akadns.net -vlportal.download.prss.microsoft.com -volic.download.prss.microsoft.com -vscode.download.prss.microsoft.com -vz.download.prss.microsoft.com -wear.googleapis.com -weather-data.apple.com -weather-data.apple.com.akadns.net -weather-map.apple.com -weather-map2.apple.com -weatherkit.apple.com -windbg.download.prss.microsoft.com -www-csb.dell.com -www-google-analytics.l.google.com -www-googletagmanager.l.google.com -www.amd.com -www.apple.com -www.apple.com.edgekey.net.globalredir.akadns.net -www.dell.com -www.destinationurl.com -www.entrust.cn -www.gstatic.com -www.pxcc.com -www.recaptcha-cn.net -www.recaptcha.net -www.support.apple.com -xp.apple.com -xtratime.qcomgeo2.com -xz.pphimalayanrt.com diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.ver b/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.ver index f16503908..e1bc5b0fe 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.ver +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/china_list.ver @@ -1 +1 @@ -202607052256 +202606132337 diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.txt b/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.txt index 5a3c859c8..e754c6768 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.txt +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.txt @@ -13,7 +13,6 @@ 173ng.com 18board.com 18comic.org -18mh.net 1984bbs.com 1991way.com 1e100.net @@ -74,7 +73,6 @@ 9cache.com 9gag.com 9news.com.au -9oju31.launches.appsflyersdk.com a-normal-day.com a248.e.akamai.net aamacau.com @@ -89,7 +87,6 @@ about.gitlab.com about.me abplive.com acast.com -accountboy.com accountkit.com acevpn.com acg.mn @@ -131,7 +128,6 @@ aiosearch.com aiph.net airconsole.com airitilibrary.com -airuniversity.af.edu airvpn.org ait.org.tw aiweiweiblog.com @@ -211,18 +207,13 @@ aoxvpn.com apartmentratings.com apartments.com apat1989.org -api-gl.lbkrs.com api-secure.recaptcha.net api-verify.recaptcha.net api.ai -api.futunn.com -api.lbkrs.com -api.moomoobull.com api.palworldgame.com api.pureapk.com api.recaptcha.net api.steampowered.com -api5.futunn.com apiary.io apigee.com apk.support @@ -400,7 +391,6 @@ bigjapanesesex.com bignews.org bigone.com bild.de -bilinovel.com biliworld.com binance.com binance.org @@ -665,7 +655,6 @@ cdjp.org cdn-images.mailchimp.com cdn-telegram.org cdn.arstechnica.net -cdn.futustatic.com cdn.jwplayer.com cdn.printfriendly.com cdn.seatguru.com @@ -689,7 +678,6 @@ centurys.net certificate-transparency.org certificate.revocationcheck.com cfr.org -cg.play-analytics.com cgdepot.org change.org changp.com @@ -871,7 +859,6 @@ crazypool.org crazyshit.com crbug.com crchina.org -creader.com creaders.net creativelab5.com cristyli.com @@ -1037,7 +1024,6 @@ dougscripts.com doujincafe.com dowjones.io download.dappcdn.com -download.futunn.com dpool.top dpp.org.tw dpr.info @@ -1163,7 +1149,6 @@ expatshield.com expecthim.com expekt.com exploader.net -expressnews.com expressvpn.com exrates.me extmatrix.com @@ -1354,36 +1339,7 @@ funkyimg.com furbo.org furinkan.com furrybar.com -futu.cn -futu.com -futu.link -futu0.com -futu1.com -futu2.com -futu3.com -futu4.com -futu5.com -futu6.com -futu7.com -futu8.com -futu9.com -futuau.com -futubull.cn -futuchain.com -futuesop.com -futufin.com -futuhk.com -futuhk2.com -futuhkapp.com -futuhn.com -futuholdings.com -futuinc.com -futuniuniu.com -futunn.com futuremessage.org -futustatic.com -fututrade.com -fututrustee.com fw.cm fxcm-chinese.com fxnetworks.com @@ -1406,7 +1362,6 @@ gaoming.net gaopi.net gartlive.com garudalinux.org -gate.com gate.io gatecoin.com gather.com @@ -1427,7 +1382,6 @@ generated.photos genius.com geph.io get.app -get.dappcdn.com get.dev get.how get.page @@ -1785,7 +1739,6 @@ gtv.org gtv1.org gu-chu-sum.org guaguass.com -guangming.com.my guishan.org gumroad.com gunsamerica.com @@ -1876,7 +1829,6 @@ hkgolden.com hkgpao.com hklts.org.hk hkmap.live -hkong.hk hkopentv.com hkpeanut.com hkreporter.com @@ -2091,7 +2043,6 @@ itasoftware.com itch.io itemfix.com itiger.com -itigerup.com itshidden.com itweet.net iuhrdf.org @@ -2181,7 +2132,6 @@ kenengba.com kepard.com kex.com keycdn.com -kfor.com khatrimaza.org kichiku-doujinko.com kik.com @@ -2231,15 +2181,12 @@ landofhope.tv lantern.io laogai.org laogairesearch.org -laohu8.com laqingdan.net larsgeorge.com lastcombat.com lastfm.es lausan.hk lbank.info -lbctrl.com -lbkrs.com ldplayer.net ldplayer.tw le-vpn.com @@ -2308,15 +2255,6 @@ localdomain.ws lockestek.com login.target.com logos.com.hk -longbridge.cloud -longbridge.com -longbridge.global -longbridge.hk -longbridge.sg -longbridgeapp.com -longbridgehk.com -longportapp.cn -longportapp.com longtermly.net longtoes.com lookpic.com @@ -2434,7 +2372,6 @@ mfxmedia.com mgoon.com mgstage.com mh4u.org -mhwindow.org microvpn.com mihua.org mij.rip @@ -2504,9 +2441,6 @@ monitorchina.org monocloud.me monster.com moodyz.com -moomoo.com -moomooequity.com -moomootrustee.com moon.fm moonbbs.com moonbingo.com @@ -2747,13 +2681,6 @@ ooni.io ooni.org open.firstory.me openai.com -openapi-quote.longbridge.cn -openapi-quote.longbridge.com -openapi-trade.longbridge.cn -openapi-trade.longbridge.com -openapi.futunn.com -openapi.longbridge.cn -openapi.longbridge.com openart.ai opencritic.com opendemocracy.net @@ -2819,7 +2746,6 @@ pbs.org pbworks.com pbxes.com pbxes.org -pcgamesn.com pcgamestorrents.com pcij.org pct.org.tw @@ -2838,7 +2764,6 @@ peoplenews.tw peopo.org perfect-privacy.com periscope.tv -perma.cc perplexity.ai pewresearch.org phayul.com @@ -3010,15 +2935,12 @@ qmp4.com qoos.com qq.co.za qstatus.com -qtcard.futunn.com -qtcardfthk.futufin.com qtrac.eu questvisual.com quitccp.org quiz.directory quora.com quoracdn.net -quote.longbridge.global quran.com qz.com r-pool.net @@ -3087,7 +3009,6 @@ rentry.co renyurenquan.org resilio.com resistchina.org -restofworld.org retweetrank.com reuters.com reutersmedia.net @@ -3161,10 +3082,6 @@ saveuighur.org sbme.me sbti.unun.dev schema.org -schwab.co.uk -schwab.com -schwab.com.cn -schwab.com.hk scmp.com scramble.io scratch.mit.edu @@ -3203,7 +3120,6 @@ sexinsex.net sf.net sfshibao.com sftuk.org -sgqt0j.launches.appsflyersdk.com shadeyouvpn.com shadowsocks.be shadowsocks.com.hk @@ -3228,7 +3144,6 @@ shodanhq.com shooshtime.com shopee.tw shopping.yahoo.co.jp -shortconn.im.qcloud.com showwe.tw shutterstock.com shwchurch.org @@ -3253,7 +3168,6 @@ sinoants.com sinoca.com sinocast.com sinoinsider.com -sinyalee.com sipml5.org sis001.com site.new @@ -3323,7 +3237,6 @@ southmongolia.org southnews.com.tw southpark.cc.com sowers.org.hk -spaceforce.mil spaces.hightail.com spacex.com spankbang.com @@ -3414,8 +3327,6 @@ supchina.com superpages.com supervpn.net superzooi.com -support.futunn.com -supremecourt.gov suprememastertv.com surfeasy.com surfeasy.com.au @@ -3527,7 +3438,6 @@ thehansindia.com thehindu.com thehun.net theinitium.com -theintercept.com thenewslens.com thepiratebay.org theporndude.com @@ -3606,12 +3516,6 @@ tibettelegraph.com tibettimes.net tibettruth.com tibetwrites.org -tigerbbs.cn -tigerbbs.com -tigerbrokers.com -tigerbrokers.net -tigerbrokers.nz -tigerfintech.com tigervpn.com tiktok.com tiktokcdn-eu.com @@ -3674,9 +3578,6 @@ totalvpn.com tou.tv tpi.org.tw tracfone.com -trade.futunn.com -trade.longportapp.com -tradeup.com tradingview.com translate.goog transparency.org @@ -3685,7 +3586,6 @@ trendsmap.com tronscan.org trouw.nl trt.net.tr -trtworld.com truebuddha-md.org trustwallet.com truthsocial.com @@ -3781,7 +3681,6 @@ twitturk.com twitturly.com twkan.com twreporter.org -twstalker.com twt.tl twtkr.com twttr.com @@ -3996,7 +3895,6 @@ wanz-factory.com warroom.org waselpro.com washingtonpost.com -washingtontimes.com watchinese.com watchmygf.net watchout.tw @@ -4005,7 +3903,6 @@ wav.tv waveprotocol.org waybig.com waymo.com -wbrks.com wd.bible wealth.com.tw wearn.com @@ -4015,7 +3912,6 @@ webmproject.org webpkgcache.com webrtc.org websdr.org -webshare.io website.new webwarper.net wechatlawsuit.com @@ -4185,7 +4081,6 @@ xgmyd.com xhamster.com xianjian.tw xiaohexie.com -xiaohu8.com xiaolan.me xiaoma.org xiaomi.eu @@ -4210,7 +4105,6 @@ xn--noss43i.com xn--oiq.cc xn--p8j9a0d9c9a.xn--q9jyb4c xn--u2u927b.com -xn--vuqv2cf7wzyig79c.com xnpool.com xnxx.com xpud.org @@ -4219,7 +4113,6 @@ xskywalker.com xt.com xt.pub xtube.com -xuan.com.my xuchao.net xuchao.org xuehua.us @@ -4326,8 +4219,6 @@ zhao.1984.city zhengjian.org zhengwunet.org zhenxiang.biz -zhijianfengyi.cn -zhijianfengyi.com zhizhu.top zhongguo.ca zhongguotese.net diff --git a/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.ver b/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.ver index f16503908..e1bc5b0fe 100644 --- a/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.ver +++ b/luci-app-homeproxy/root/etc/homeproxy/resources/gfw_list.ver @@ -1 +1 @@ -202607052256 +202606132337 diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc b/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc index 890d23b06..70a16948c 100755 --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc @@ -7,6 +7,7 @@ 'use strict'; +import { md5 } from 'digest'; import { readfile, writefile } from 'fs'; import { isnan } from 'math'; import { connect } from 'ubus'; @@ -29,7 +30,10 @@ uci.load(uciconfig); const uciinfra = 'infra', ucimain = 'config', - ucicontrol = 'control'; + ucicontrol = 'control', + uciclashapi = 'clash_api', + ucintp = 'ntp', + ucicache = 'cache'; const ucidnssetting = 'dns', ucidnsserver = 'dns_server', @@ -41,23 +45,63 @@ const uciroutingsetting = 'routing', const ucinode = 'node'; const uciruleset = 'ruleset'; +const RULESET_DIR = HP_DIR + '/ruleset'; const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainland_china'; +function normalize_list(value) { + if (isEmpty(value)) + return []; + if (type(value) === 'array') + return value; + return [value]; +} + +function ruleset_file_name(tag, format) { + let filename = replace(tag || 'ruleset', /[^A-Za-z0-9_.-]+/g, '_'); + if (isEmpty(filename)) + filename = 'ruleset'; + + return filename + ((format === 'source') ? '.json' : '.srs'); +} + +function ruleset_default_path(tag, format) { + return RULESET_DIR + '/' + ruleset_file_name(tag, format); +} + +function ruleset_remote_path(path, tag, format) { + if (isEmpty(path)) + return ruleset_default_path(tag, format); + + if (match(path, /\/$/)) + return path + ruleset_file_name(tag, format); + + return path; +} + let wan_dns = ubus.call('network.interface', 'status', {'interface': 'wan'})?.['dns-server']?.[0]; if (!wan_dns) - wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '8.8.8.8' : '223.5.5.5'; + wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '9.9.9.9' : '223.5.5.5'; const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333'; -const ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server') || 'time.apple.com'; +let ntp_enabled = uci.get(uciconfig, ucintp, 'enabled'), + ntp_server = uci.get(uciconfig, ucintp, 'server'), + ntp_server_port = uci.get(uciconfig, ucintp, 'server_port'), + ntp_interval = uci.get(uciconfig, ucintp, 'interval'); + +if (isEmpty(ntp_enabled) && isEmpty(ntp_server)) { + ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server'); + ntp_enabled = !isEmpty(ntp_server) ? '1' : '0'; +} const ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0'; -let main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns, +let cache_file_enabled, cache_file_path, cache_store_rdrc, cache_rdrc_timeout, + main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns, domain_strategy, sniff_override, dns_server, china_dns_server, dns_default_strategy, dns_default_server, dns_disable_cache, dns_disable_cache_expire, dns_independent_cache, - dns_client_subnet, cache_file_store_rdrc, cache_file_rdrc_timeout, direct_domain_list, + dns_client_subnet, direct_domain_list, proxy_domain_list; if (routing_mode !== 'custom') { @@ -93,8 +137,6 @@ if (routing_mode !== 'custom') { dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire'); dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache'); dns_client_subnet = uci.get(uciconfig, ucidnssetting, 'client_subnet'); - cache_file_store_rdrc = uci.get(uciconfig, ucidnssetting, 'cache_file_store_rdrc'), - cache_file_rdrc_timeout = uci.get(uciconfig, ucidnssetting, 'cache_file_rdrc_timeout'); /* Routing settings */ default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil'; @@ -110,7 +152,7 @@ const mixed_port = uci.get(uciconfig, uciinfra, 'mixed_port') || '5330'; let self_mark, redirect_port, tproxy_port, tun_name, tun_addr4, tun_addr6, tun_mtu, tcpip_stack, - endpoint_independent_nat, udp_timeout; + udp_timeout; if (routing_mode === 'custom') udp_timeout = uci.get(uciconfig, uciroutingsetting, 'udp_timeout'); @@ -130,13 +172,18 @@ if (match(proxy_mode, /tun/)) { tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126'; tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000'; tcpip_stack = 'system'; - if (routing_mode === 'custom') { + if (routing_mode === 'custom') tcpip_stack = uci.get(uciconfig, uciroutingsetting, 'tcpip_stack') || 'system'; - endpoint_independent_nat = uci.get(uciconfig, uciroutingsetting, 'endpoint_independent_nat'); - } } const log_level = uci.get(uciconfig, ucimain, 'log_level') || 'warn'; + +cache_file_enabled = uci.get(uciconfig, ucicache, 'enabled'); +cache_file_path = uci.get(uciconfig, ucicache, 'path'); +cache_store_rdrc = uci.get(uciconfig, ucicache, 'store_rdrc'); +cache_rdrc_timeout = uci.get(uciconfig, ucicache, 'rdrc_timeout'); + +let clash_api = null; /* UCI config end */ /* Config helper start */ @@ -168,6 +215,31 @@ function parse_dnsserver(server_addr, default_protocol) { } } +function parse_custom_dnsserver(cfg) { + let server = cfg.server, + server_port = (cfg.type in ['https', 'h3']) ? null : strToInt(cfg.server_port), + path = cfg.path; + + if (!isEmpty(server) && match(server, /:\/\//)) { + const server_url = parseURL(server); + + server = server_url.hostname; + if (isEmpty(server_port)) + server_port = strToInt(server_url.port); + if ((cfg.type in ['https', 'h3']) && isEmpty(path) && server_url.pathname !== '/') + path = server_url.pathname; + } + if ((cfg.type in ['https', 'h3']) && isEmpty(path)) + path = '/dns-query'; + + return { + type: cfg.type, + server, + server_port, + path + }; +} + function parse_dnsquery(strquery) { if (type(strquery) !== 'array' || isEmpty(strquery)) return null; @@ -180,13 +252,149 @@ function parse_dnsquery(strquery) { } +function filter_existing_nodes(nodes) { + if (type(nodes) !== 'array' || isEmpty(nodes)) + return []; + + return filter(nodes, (k) => { + const node = uci.get_all(uciconfig, k) || {}; + return !isEmpty(node); + }); +} + +function collect_group_nodes(groups, subscription_nodes, nodes, legacy_nodes) { + let result = []; + + for (let group in normalize_list(groups)) + uci.foreach(uciconfig, ucinode, (cfg) => { + if (cfg.grouphash === group && !~index(result, cfg['.name'])) + push(result, cfg['.name']); + }); + + for (let node in filter_existing_nodes(normalize_list(subscription_nodes))) + if (!~index(result, node)) + push(result, node); + + for (let node in filter_existing_nodes(normalize_list(nodes))) + if (!~index(result, node)) + push(result, node); + + for (let node in filter_existing_nodes(normalize_list(legacy_nodes))) + if (!~index(result, node)) + push(result, node); + + return result; +} + +function collect_policy_nodes(nodes, current) { + let result = []; + + for (let node in normalize_list(nodes)) { + if (node === current || ~index(result, node)) + continue; + + const outbound = uci.get_all(uciconfig, node) || {}; + if (!isEmpty(outbound) && outbound['.type'] === uciroutingnode && outbound.enabled === '1' && outbound.node in ['urltest', 'selector']) + push(result, node); + } + + return result; +} + +const reserved_outbound_tags = { + 'GLOBAL': true, + 'main-out': true, + 'main-udp-out': true, + 'direct-out': true, + 'block-out': true +}; + +let outbound_tag_map = {}, + outbound_tag_used = {}; + +function normalize_outbound_tag(tag) { + if (isEmpty(tag)) + return null; + + tag = trim(sprintf('%s', tag)); + tag = replace(tag, /[\r\n\t]+/g, ' '); + tag = replace(tag, /[\/\\?#%]+/g, '-'); + tag = replace(tag, /\s+/g, ' '); + + return tag || null; +} + +function section_display_name(section) { + if (type(section) !== 'object' || isEmpty(section)) + return null; + + let label = normalize_outbound_tag(section.label); + if (!isEmpty(label)) + return label; + + if (section['.type'] === ucinode) { + const address = trim(section.override_address || section.address || ''); + const port = trim(section.override_port || section.port || ''); + + if (address && port) + return normalize_outbound_tag(`${address}:${port}`); + } + + return null; +} + +function register_outbound_tag(section_id, display_name) { + if (isEmpty(section_id)) + return null; + + let tag = outbound_tag_map[section_id]; + if (!isEmpty(tag)) + return tag; + + const fallback_tag = `cfg-${section_id}-out`; + const base_tag = normalize_outbound_tag(display_name) || fallback_tag; + + tag = base_tag; + let suffix = 2; + while (reserved_outbound_tags[tag] || outbound_tag_used[tag]) { + tag = `${base_tag} (${suffix})`; + suffix++; + } + + outbound_tag_map[section_id] = tag; + outbound_tag_used[tag] = true; + + return tag; +} + +function get_section_outbound_tag(section_id) { + if (isEmpty(section_id)) + return null; + + let tag = outbound_tag_map[section_id]; + if (!isEmpty(tag)) + return tag; + + const section = uci.get_all(uciconfig, section_id) || {}; + return register_outbound_tag(section_id, section_display_name(section)); +} + +uci.foreach(uciconfig, ucinode, (cfg) => { + register_outbound_tag(cfg['.name'], section_display_name(cfg)); +}); + +uci.foreach(uciconfig, uciroutingnode, (cfg) => { + if (cfg.enabled === '1') + register_outbound_tag(cfg['.name'], section_display_name(cfg)); +}); + function generate_endpoint(node) { if (type(node) !== 'object' || isEmpty(node)) return null; const endpoint = { type: node.type, - tag: 'cfg-' + node['.name'] + '-out', + tag: get_section_outbound_tag(node['.name']), address: node.wireguard_local_address, mtu: strToInt(node.wireguard_mtu), private_key: node.wireguard_private_key, @@ -217,13 +425,14 @@ function generate_outbound(node) { if (type(node) !== 'object' || isEmpty(node)) return null; + const tls_utls_value = (node.type === 'anytls' && isEmpty(node.tls_utls)) ? 'chrome' : node.tls_utls; const outbound = { type: node.type, - tag: 'cfg-' + node['.name'] + '-out', + tag: get_section_outbound_tag(node['.name']), routing_mark: strToInt(self_mark), - server: node.address, - server_port: strToInt(node.port), + server: (node.type !== 'direct') ? node.address : null, + server_port: (node.type !== 'direct') ? strToInt(node.port) : null, /* Hysteria(2) */ server_ports: node.hysteria_hopping_port, @@ -231,10 +440,6 @@ function generate_outbound(node) { user: (node.type === 'ssh') ? node.username : null, password: node.password, - /* Direct */ - override_address: node.override_address, - override_port: strToInt(node.override_port), - proxy_protocol: strToInt(node.proxy_protocol), /* AnyTLS */ idle_session_check_interval: strToTime(node.anytls_idle_session_check_interval), idle_session_timeout: strToTime(node.anytls_idle_session_timeout), @@ -306,9 +511,9 @@ function generate_outbound(node) { config: node.tls_ech_config, config_path: node.tls_ech_config_path } : null, - utls: !isEmpty(node.tls_utls) ? { + utls: !isEmpty(tls_utls_value) ? { enabled: true, - fingerprint: node.tls_utls + fingerprint: tls_utls_value } : null, reality: (node.tls_reality === '1') ? { enabled: true, @@ -364,10 +569,10 @@ function get_outbound(cfg) { const node = uci.get(uciconfig, cfg, 'node'); if (isEmpty(node)) die(sprintf("%s's node is missing, please check your configuration.", cfg)); - else if (node === 'urltest') - return 'cfg-' + cfg + '-out'; + else if (node in ['urltest', 'selector']) + return get_section_outbound_tag(cfg); else - return 'cfg-' + node + '-out'; + return get_section_outbound_tag(node); } } } @@ -390,10 +595,117 @@ function get_ruleset(cfg) { return null; let rules = []; - for (let i in cfg) - push(rules, isEmpty(i) ? null : 'cfg-' + i + '-rule'); + for (let i in cfg) { + if (isEmpty(i)) { + push(rules, null); + continue; + } + + const ruleset = uci.get_all(uciconfig, i); + push(rules, !isEmpty(ruleset?.tag) ? ruleset.tag : ('cfg-' + i + '-rule')); + } return rules; } + +function subscription_provider_name(name) { + name = trim(name || ''); + return name; +} + +function unique_provider_name(name, used) { + const base = name; + let suffix = 2; + + while (used[name]) { + name = sprintf('%s (%d)', base, suffix); + suffix++; + } + used[name] = true; + + return name; +} + +function get_subscription_info(group_hash) { + const value = uci.get(uciconfig, 'subscription', 'subinfo_' + substr(group_hash, 0, 16)); + if (isEmpty(value)) + return null; + + try { + const info = json(value); + if (type(info) !== 'object') + return null; + + const has_subscription_info = ('upload' in info) || ('download' in info) || + ('total' in info) || ('expire' in info); + + return { + updated_at: info.updated_at, + subscription_info: has_subscription_info ? { + upload: info.upload, + download: info.download, + total: info.total, + expire: info.expire + } : null + }; + } catch (e) { + return null; + } +} + +function build_proxy_providers() { + let providers = [], + used_names = {}, + subscription_names = normalize_list(uci.get(uciconfig, 'subscription', 'subscription_name')), + sub_index = 0; + + for (let suburl in normalize_list(uci.get(uciconfig, 'subscription', 'subscription_url'))) { + const subscription_name = subscription_names[sub_index]; + sub_index++; + + if (isEmpty(suburl) || isEmpty(trim(subscription_name || ''))) + continue; + + const url = replace(suburl, /#.*$/, ''), + group_hash = md5(url); + + let proxies = []; + uci.foreach(uciconfig, ucinode, (cfg) => { + if (cfg.grouphash !== group_hash) + return; + + const tag = get_section_outbound_tag(cfg['.name']); + if (!isEmpty(tag) && !~index(proxies, tag)) + push(proxies, tag); + }); + + if (isEmpty(proxies)) + continue; + + const subscription_info = get_subscription_info(group_hash); + + push(providers, { + name: unique_provider_name(subscription_provider_name(subscription_name), used_names), + type: 'Proxy', + vehicle_type: 'HTTP', + proxies: proxies, + update_id: (routing_mode === 'custom') ? group_hash : null, + updated_at: subscription_info?.updated_at, + subscription_info: subscription_info?.subscription_info + }); + } + + return providers; +} + +clash_api = { + external_controller: uci.get(uciconfig, uciclashapi, 'external_controller') || '127.0.0.1:9090', + external_ui: uci.get(uciconfig, uciclashapi, 'external_ui'), + external_ui_download_url: uci.get(uciconfig, uciclashapi, 'external_ui_download_url'), + external_ui_download_detour: get_outbound(uci.get(uciconfig, uciclashapi, 'external_ui_download_detour')), + secret: uci.get(uciconfig, uciclashapi, 'secret'), + default_mode: uci.get(uciconfig, uciclashapi, 'default_mode') || 'rule', + proxy_providers: build_proxy_providers() +}; /* Config helper end */ const config = {}; @@ -409,8 +721,10 @@ config.log = { /* NTP */ if (!isEmpty(ntp_server)) config.ntp = { - enabled: true, + enabled: ntp_enabled === '1', server: ntp_server, + server_port: strToInt(ntp_server_port), + interval: ntp_interval, detour: 'direct-out', domain_resolver: 'default-dns', }; @@ -520,10 +834,7 @@ if (!isEmpty(main_node)) { push(config.dns.servers, { tag: 'cfg-' + cfg['.name'] + '-dns', - type: cfg.type, - server: cfg.server, - server_port: strToInt(cfg.server_port), - path: cfg.path, + ...parse_custom_dnsserver(cfg), headers: cfg.headers, tls: cfg.tls_sni ? { enabled: true, @@ -565,7 +876,6 @@ if (!isEmpty(main_node)) { user: cfg.user, rule_set: get_ruleset(cfg.rule_set), rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source), - rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty), invert: strToBool(cfg.invert), outbound: get_outbound(cfg.outbound), action: cfg.action, @@ -606,8 +916,6 @@ push(config.inbounds, { listen: '::', listen_port: int(mixed_port), udp_timeout: strToTime(udp_timeout), - sniff: true, - sniff_override_destination: strToBool(sniff_override), set_system_proxy: false }); @@ -617,9 +925,7 @@ if (match(proxy_mode, /redirect/)) tag: 'redirect-in', listen: '::', - listen_port: int(redirect_port), - sniff: true, - sniff_override_destination: strToBool(sniff_override) + listen_port: int(redirect_port) }); if (match(proxy_mode, /tproxy/)) push(config.inbounds, { @@ -629,9 +935,7 @@ if (match(proxy_mode, /tproxy/)) listen: '::', listen_port: int(tproxy_port), network: 'udp', - udp_timeout: strToTime(udp_timeout), - sniff: true, - sniff_override_destination: strToBool(sniff_override) + udp_timeout: strToTime(udp_timeout) }); if (match(proxy_mode, /tun/)) push(config.inbounds, { @@ -642,11 +946,8 @@ if (match(proxy_mode, /tun/)) address: (ipv6_support === '1') ? [tun_addr4, tun_addr6] : [tun_addr4], mtu: strToInt(tun_mtu), auto_route: false, - endpoint_independent_nat: strToBool(endpoint_independent_nat), udp_timeout: strToTime(udp_timeout), - stack: tcpip_stack, - sniff: true, - sniff_override_destination: strToBool(sniff_override) + stack: tcpip_stack }); /* Inbound end */ @@ -671,14 +972,16 @@ if (!isEmpty(main_node)) { let urltest_nodes = []; if (main_node === 'urltest') { - const main_urltest_nodes = uci.get(uciconfig, ucimain, 'main_urltest_nodes') || []; + const main_urltest_nodes = filter_existing_nodes( + normalize_list(uci.get(uciconfig, ucimain, 'main_urltest_nodes')) + ); const main_urltest_interval = uci.get(uciconfig, ucimain, 'main_urltest_interval'); const main_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_urltest_tolerance'); push(config.outbounds, { type: 'urltest', tag: 'main-out', - outbounds: map(main_urltest_nodes, (k) => `cfg-${k}-out`), + outbounds: map(main_urltest_nodes, (k) => get_section_outbound_tag(k)), interval: strToTime(main_urltest_interval), tolerance: strToInt(main_urltest_tolerance), idle_timeout: (strToInt(main_urltest_interval) > 1800) ? `${main_urltest_interval * 2}s` : null, @@ -687,23 +990,31 @@ if (!isEmpty(main_node)) { } else { const main_node_cfg = uci.get_all(uciconfig, main_node) || {}; if (main_node_cfg.type === 'wireguard') { - push(config.endpoints, generate_endpoint(main_node_cfg)); - config.endpoints[length(config.endpoints)-1].tag = 'main-out'; + const main_endpoint = generate_endpoint(main_node_cfg); + if (main_endpoint) { + main_endpoint.tag = 'main-out'; + push(config.endpoints, main_endpoint); + } } else { - push(config.outbounds, generate_outbound(main_node_cfg)); - config.outbounds[length(config.outbounds)-1].tag = 'main-out'; + const main_outbound = generate_outbound(main_node_cfg); + if (main_outbound) { + main_outbound.tag = 'main-out'; + push(config.outbounds, main_outbound); + } } } if (main_udp_node === 'urltest') { - const main_udp_urltest_nodes = uci.get(uciconfig, ucimain, 'main_udp_urltest_nodes') || []; + const main_udp_urltest_nodes = filter_existing_nodes( + normalize_list(uci.get(uciconfig, ucimain, 'main_udp_urltest_nodes')) + ); const main_udp_urltest_interval = uci.get(uciconfig, ucimain, 'main_udp_urltest_interval'); const main_udp_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_udp_urltest_tolerance'); push(config.outbounds, { type: 'urltest', tag: 'main-udp-out', - outbounds: map(main_udp_urltest_nodes, (k) => `cfg-${k}-out`), + outbounds: map(main_udp_urltest_nodes, (k) => get_section_outbound_tag(k)), interval: strToTime(main_udp_urltest_interval), tolerance: strToInt(main_udp_urltest_tolerance), idle_timeout: (strToInt(main_udp_urltest_interval) > 1800) ? `${main_udp_urltest_interval * 2}s` : null, @@ -712,22 +1023,37 @@ if (!isEmpty(main_node)) { } else if (dedicated_udp_node) { const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {}; if (main_udp_node_cfg.type === 'wireguard') { - push(config.endpoints, generate_endpoint(main_udp_node_cfg)); - config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out'; + const main_udp_endpoint = generate_endpoint(main_udp_node_cfg); + if (main_udp_endpoint) { + main_udp_endpoint.tag = 'main-udp-out'; + push(config.endpoints, main_udp_endpoint); + } } else { - push(config.outbounds, generate_outbound(main_udp_node_cfg)); - config.outbounds[length(config.outbounds)-1].tag = 'main-udp-out'; + const main_udp_outbound = generate_outbound(main_udp_node_cfg); + if (main_udp_outbound) { + main_udp_outbound.tag = 'main-udp-out'; + push(config.outbounds, main_udp_outbound); + } } } for (let i in urltest_nodes) { const urltest_node = uci.get_all(uciconfig, i) || {}; + if (isEmpty(urltest_node)) + continue; + if (urltest_node.type === 'wireguard') { - push(config.endpoints, generate_endpoint(urltest_node)); - config.endpoints[length(config.endpoints)-1].tag = 'cfg-' + i + '-out'; + const endpoint = generate_endpoint(urltest_node); + if (endpoint) { + endpoint.tag = get_section_outbound_tag(i); + push(config.endpoints, endpoint); + } } else { - push(config.outbounds, generate_outbound(urltest_node)); - config.outbounds[length(config.outbounds)-1].tag = 'cfg-' + i + '-out'; + const outbound = generate_outbound(urltest_node); + if (outbound) { + outbound.tag = get_section_outbound_tag(i); + push(config.outbounds, outbound); + } } } } else if (!isEmpty(default_outbound)) { @@ -739,37 +1065,63 @@ if (!isEmpty(main_node)) { return; if (cfg.node === 'urltest') { + const legacy_nodes = (isEmpty(cfg.subscription_groups) && isEmpty(cfg.subscription_nodes) && isEmpty(cfg.selected_nodes)) ? cfg.urltest_nodes : null; + const urltest_list = collect_group_nodes(cfg.subscription_groups, cfg.subscription_nodes, cfg.selected_nodes, legacy_nodes); push(config.outbounds, { type: 'urltest', - tag: 'cfg-' + cfg['.name'] + '-out', - outbounds: map(cfg.urltest_nodes, (k) => `cfg-${k}-out`), + tag: get_section_outbound_tag(cfg['.name']), + outbounds: map(urltest_list, (k) => get_section_outbound_tag(k)), url: cfg.urltest_url, interval: strToTime(cfg.urltest_interval), tolerance: strToInt(cfg.urltest_tolerance), idle_timeout: strToTime(cfg.urltest_idle_timeout), interrupt_exist_connections: strToBool(cfg.urltest_interrupt_exist_connections) }); - urltest_nodes = [...urltest_nodes, ...filter(cfg.urltest_nodes, (l) => !~index(urltest_nodes, l))]; + urltest_nodes = [...urltest_nodes, ...filter(urltest_list, (l) => !~index(urltest_nodes, l))]; + } else if (cfg.node === 'selector') { + const selector_node_list = collect_group_nodes(cfg.subscription_groups, cfg.subscription_nodes, cfg.selected_nodes, null); + const selector_policy_list = collect_policy_nodes(cfg.policy_nodes, cfg['.name']); + const selector_list = [...selector_node_list, ...filter(selector_policy_list, (l) => !~index(selector_node_list, l))]; + const selector_default = (!isEmpty(cfg.selector_default) && ~index(selector_list, cfg.selector_default)) ? cfg.selector_default : null; + push(config.outbounds, { + type: 'selector', + tag: get_section_outbound_tag(cfg['.name']), + outbounds: map(selector_list, (k) => get_section_outbound_tag(k)), + default: selector_default ? get_section_outbound_tag(selector_default) : null, + interrupt_exist_connections: strToBool(cfg.selector_interrupt_exist_connections) + }); + urltest_nodes = [...urltest_nodes, ...filter(selector_node_list, (l) => !~index(urltest_nodes, l))]; } else { const outbound = uci.get_all(uciconfig, cfg.node) || {}; + if (isEmpty(outbound)) + return; + if (outbound.type === 'wireguard') { - push(config.endpoints, generate_endpoint(outbound)); - config.endpoints[length(config.endpoints)-1].bind_interface = cfg.bind_interface; - config.endpoints[length(config.endpoints)-1].detour = get_outbound(cfg.outbound); + const endpoint = generate_endpoint(outbound); + if (!endpoint) + return; + + endpoint.bind_interface = cfg.bind_interface; + endpoint.detour = get_outbound(cfg.outbound); if (cfg.domain_resolver) - config.endpoints[length(config.endpoints)-1].domain_resolver = { + endpoint.domain_resolver = { server: get_resolver(cfg.domain_resolver), strategy: cfg.domain_strategy }; + push(config.endpoints, endpoint); } else { - push(config.outbounds, generate_outbound(outbound)); - config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface; - config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound); + const routed_outbound = generate_outbound(outbound); + if (!routed_outbound) + return; + + routed_outbound.bind_interface = cfg.bind_interface; + routed_outbound.detour = get_outbound(cfg.outbound); if (cfg.domain_resolver) - config.outbounds[length(config.outbounds)-1].domain_resolver = { + routed_outbound.domain_resolver = { server: get_resolver(cfg.domain_resolver), strategy: cfg.domain_strategy }; + push(config.outbounds, routed_outbound); } push(routing_nodes, cfg.node); } @@ -777,10 +1129,18 @@ if (!isEmpty(main_node)) { for (let i in filter(urltest_nodes, (l) => !~index(routing_nodes, l))) { const urltest_node = uci.get_all(uciconfig, i) || {}; - if (urltest_node.type === 'wireguard') - push(config.endpoints, generate_endpoint(urltest_node)); - else - push(config.outbounds, generate_outbound(urltest_node)); + if (isEmpty(urltest_node)) + continue; + + if (urltest_node.type === 'wireguard') { + const endpoint = generate_endpoint(urltest_node); + if (endpoint) + push(config.endpoints, endpoint); + } else { + const outbound = generate_outbound(urltest_node); + if (outbound) + push(config.outbounds, outbound); + } } } @@ -795,13 +1155,10 @@ config.route = { { inbound: 'dns-in', action: 'hijack-dns' + }, + { + action: 'sniff' } - /* - * leave for sing-box 1.13.0 - * { - * action: 'sniff' - * } - */ ], rule_set: [], auto_detect_interface: isEmpty(default_interface) ? true : null, @@ -866,6 +1223,7 @@ if (!isEmpty(main_node)) { tag: 'geoip-cn', format: 'binary', url: 'https://fastly.jsdelivr.net/gh/1715173329/IPCIDR-CHINA@rule-set/cn.srs', + path: ruleset_default_path('geoip-cn', 'binary'), download_detour: 'main-out' }); push(config.route.rule_set, { @@ -873,6 +1231,7 @@ if (!isEmpty(main_node)) { tag: 'geosite-cn', format: 'binary', url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-cn.srs', + path: ruleset_default_path('geosite-cn', 'binary'), download_detour: 'main-out' }); push(config.route.rule_set, { @@ -880,6 +1239,7 @@ if (!isEmpty(main_node)) { tag: 'geosite-noncn', format: 'binary', url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-!cn.srs', + path: ruleset_default_path('geosite-noncn', 'binary'), download_detour: 'main-out' }); } @@ -924,17 +1284,25 @@ if (!isEmpty(main_node)) { user: cfg.user, rule_set: get_ruleset(cfg.rule_set), rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source), + rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty), invert: strToBool(cfg.invert), action: cfg.action, - outbound: get_outbound(cfg.outbound), - override_address: cfg.override_address, - override_port: strToInt(cfg.override_port), - udp_disable_domain_unmapping: strToBool(cfg.udp_disable_domain_unmapping), - udp_connect: strToBool(cfg.udp_connect), - udp_timeout: strToTime(cfg.udp_timeout), - tls_fragment: strToBool(cfg.tls_fragment), - tls_fragment_fallback_delay: strToTime(cfg.tls_fragment_fallback_delay), - tls_record_fragment: strToBool(cfg.tls_record_fragment) + outbound: (cfg.action === 'route') ? get_outbound(cfg.outbound) : null, + server: (cfg.action === 'resolve') ? get_resolver(cfg.resolve_server) : null, + strategy: (cfg.action === 'resolve') ? cfg.resolve_strategy : null, + disable_cache: (cfg.action === 'resolve') ? strToBool(cfg.resolve_disable_cache) : null, + rewrite_ttl: (cfg.action === 'resolve') ? strToInt(cfg.resolve_rewrite_ttl) : null, + client_subnet: (cfg.action === 'resolve') ? cfg.resolve_client_subnet : null, + override_address: (cfg.action in ['route', 'route-options']) ? cfg.override_address : null, + override_port: (cfg.action in ['route', 'route-options']) ? strToInt(cfg.override_port) : null, + udp_disable_domain_unmapping: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.udp_disable_domain_unmapping) : null, + udp_connect: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.udp_connect) : null, + udp_timeout: (cfg.action in ['route', 'route-options']) ? strToTime(cfg.udp_timeout) : null, + tls_fragment: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.tls_fragment) : null, + tls_fragment_fallback_delay: (cfg.action in ['route', 'route-options']) ? strToTime(cfg.tls_fragment_fallback_delay) : null, + tls_record_fragment: (cfg.action in ['route', 'route-options']) ? strToBool(cfg.tls_record_fragment) : null, + method: (cfg.action === 'reject') ? cfg.reject_method : null, + no_drop: (cfg.action === 'reject') ? strToBool(cfg.reject_no_drop) : null }); }); @@ -945,14 +1313,16 @@ if (!isEmpty(main_node)) { if (cfg.enabled !== '1') return null; + const tag = !isEmpty(cfg.tag) ? cfg.tag : ('cfg-' + cfg['.name'] + '-rule'); + push(config.route.rule_set, { type: cfg.type, - tag: 'cfg-' + cfg['.name'] + '-rule', + tag: tag, format: cfg.format, - path: cfg.path, url: cfg.url, + path: (cfg.type === 'remote') ? ruleset_remote_path(cfg.remote_path || cfg.path, tag, cfg.format) : cfg.path, download_detour: get_outbound(cfg.outbound), - update_interval: cfg.update_interval + update_interval: (cfg.type === 'remote') ? '87600h' : null }); }); } @@ -961,15 +1331,17 @@ if (!isEmpty(main_node)) { /* Experimental start */ if (routing_mode in ['bypass_mainland_china', 'custom']) { config.experimental = { + clash_api, cache_file: { - enabled: true, - path: RUN_DIR + '/cache.db', - store_rdrc: strToBool(cache_file_store_rdrc), - rdrc_timeout: strToTime(cache_file_rdrc_timeout), + enabled: cache_file_enabled !== '0', + path: cache_file_path || (HP_DIR + '/cache.db'), + store_rdrc: strToBool(cache_store_rdrc), + rdrc_timeout: strToTime(cache_rdrc_timeout), } }; } /* Experimental end */ system('mkdir -p ' + RUN_DIR); +system('mkdir -p ' + RULESET_DIR); writefile(RUN_DIR + '/sing-box-c.json', sprintf('%.J\n', removeBlankAttrs(config))); diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_server.uc b/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_server.uc index 224d72503..7941afa6f 100755 --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_server.uc +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_server.uc @@ -67,7 +67,7 @@ uci.foreach(uciconfig, uciserver, (cfg) => { password: cfg.hysteria_obfs_password } : cfg.hysteria_obfs_password, recv_window_conn: strToInt(cfg.hysteria_recv_window_conn), - recv_window_client: strToInt(cfg.hysteria_revc_window_client), + recv_window_client: strToInt(cfg.hysteria_recv_window_client), max_conn_client: strToInt(cfg.hysteria_max_conn_client), disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery), ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth), diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc b/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc old mode 100644 new mode 100755 index fb2743202..8ff55bf7f --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc @@ -66,15 +66,34 @@ export function getTime(epoch) { }; -export function wGET(url, ua) { +export function wGETResponse(url, ua) { if (!url || type(url) !== 'string') return null; if (!ua) ua = 'Wget/1.21 (HomeProxy, like v2rayN)'; - const output = executeCommand(`/usr/bin/wget -qO- --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {}; - return trim(output.stdout); + const output = executeCommand(`/usr/bin/wget -O- -S --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {}; + return { + body: trim(output.stdout), + headers: output.stderr || '', + exitcode: output.exitcode + }; +}; + +export function wGET(url, ua) { + return wGETResponse(url, ua)?.body; +}; + +export function wGETHeaders(url, ua) { + if (!url || type(url) !== 'string') + return null; + + if (!ua) + ua = 'clash.meta'; + + const output = executeCommand(`/usr/bin/wget -O /dev/null -S --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {}; + return output.stderr || ''; }; /* Utilities end */ diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/migrate_config.uc b/luci-app-homeproxy/root/etc/homeproxy/scripts/migrate_config.uc index 7cfeb3fbf..5f6278787 100755 --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/migrate_config.uc +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/migrate_config.uc @@ -7,14 +7,34 @@ 'use strict'; +import { popen } from 'fs'; import { cursor } from 'uci'; -import { isEmpty, parseURL, validation } from 'homeproxy'; +import { isEmpty, parseURL } from 'homeproxy'; const uci = cursor(); const uciconfig = 'homeproxy'; uci.load(uciconfig); +function random_secret() { + const fd = popen('tr -dc "A-Za-z0-9" < /dev/urandom 2>/dev/null | head -c 16'); + let random = ''; + if (fd) { + random = trim(fd.read('all') || ''); + fd.close(); + } + + if (isEmpty(random)) { + const fallback = popen('awk \'BEGIN{srand(); printf "%06d", int(rand() * 1000000)}\''); + if (fallback) { + random = trim(fallback.read('all') || ''); + fallback.close(); + } + } + + return random; +} + const uciinfra = 'infra', ucimigration = 'migration', ucimain = 'config', @@ -25,8 +45,16 @@ const uciinfra = 'infra', ucirouting = 'routing', uciroutingnode = 'routing_node', uciroutingrule = 'routing_rule', + uciclashapi = 'clash_api', + ucintp = 'ntp', + ucicache = 'cache', uciserver = 'server'; +function is_update_cron(value) { + let matched = match(value || '', /^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+([0-7*])$/); + return !isEmpty(matched) && int(matched[1]) <= 59 && int(matched[2]) <= 23; +} + /* chinadns-ng has been removed */ if (uci.get(uciconfig, uciinfra, 'china_dns_port')) uci.delete(uciconfig, uciinfra, 'china_dns_port'); @@ -36,9 +64,7 @@ const china_dns_server = uci.get(uciconfig, ucimain, 'china_dns_server'); if (type(china_dns_server) === 'array') { uci.set(uciconfig, ucimain, 'china_dns_server', china_dns_server[0]); } else { - if (china_dns_server === 'wan_114') - uci.set(uciconfig, ucimain, 'china_dns_server', '114.114.114.114'); - else if (match(china_dns_server, /,/)) + if (match(china_dns_server, /,/)) uci.set(uciconfig, ucimain, 'china_dns_server', split(china_dns_server, ',')[0]); } @@ -57,6 +83,93 @@ if (!uci.get(uciconfig, uciinfra, 'ntp_server')) if (!isEmpty(uci.get(uciconfig, uciinfra, 'tun_gso'))) uci.delete(uciconfig, uciinfra, 'tun_gso'); +/* endpoint_independent_nat was removed in sing-box 1.13 */ +if (!isEmpty(uci.get(uciconfig, ucirouting, 'endpoint_independent_nat'))) + uci.delete(uciconfig, ucirouting, 'endpoint_independent_nat'); + +/* direct outbound proxy_protocol was removed in sing-box 1.13 */ +uci.foreach(uciconfig, ucinode, (cfg) => { + if (cfg.type === 'direct' && !isEmpty(cfg.proxy_protocol)) + uci.delete(uciconfig, cfg['.name'], 'proxy_protocol'); +}); + +const legacy_panel_url = 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip'; +const legacy_panel_proxy_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip'; +const old_panel_url = 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip'; +const old_panel_proxy_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip'; +const default_panel_url = 'https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip'; + +/* clash api panel options were introduced */ +if (!uci.get(uciconfig, uciclashapi)) + uci.set(uciconfig, uciclashapi, uciconfig); + +if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_ui'))) + uci.set(uciconfig, uciclashapi, 'external_ui', '/etc/homeproxy/run/ui'); + +const panel_url = uci.get(uciconfig, uciclashapi, 'external_ui_download_url'); +if (isEmpty(panel_url) || panel_url === legacy_panel_url || panel_url === legacy_panel_proxy_url || panel_url === old_panel_proxy_url) + uci.set(uciconfig, uciclashapi, 'external_ui_download_url', default_panel_url); + +if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_ui_download_detour'))) + uci.set(uciconfig, uciclashapi, 'external_ui_download_detour', 'direct-out'); + +if (isEmpty(uci.get(uciconfig, uciclashapi, 'external_controller'))) + uci.set(uciconfig, uciclashapi, 'external_controller', '0.0.0.0:9095'); + +if (isEmpty(uci.get(uciconfig, uciclashapi, 'secret')) || uci.get(uciconfig, uciclashapi, 'secret') === 'homeproxy') { + let random = random_secret(); + if (!isEmpty(random)) + uci.set(uciconfig, uciclashapi, 'secret', random); +} + +if (isEmpty(uci.get(uciconfig, uciclashapi, 'default_mode'))) + uci.set(uciconfig, uciclashapi, 'default_mode', 'rule'); + +/* ntp options were moved into a dedicated section */ +if (!uci.get(uciconfig, ucintp)) + uci.set(uciconfig, ucintp, uciconfig); + +const legacy_ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server'); +if (isEmpty(uci.get(uciconfig, ucintp, 'enabled'))) + uci.set(uciconfig, ucintp, 'enabled', '1'); + +if (isEmpty(uci.get(uciconfig, ucintp, 'server'))) + uci.set(uciconfig, ucintp, 'server', !isEmpty(legacy_ntp_server) ? legacy_ntp_server : 'ntp.aliyun.com'); + +if (isEmpty(uci.get(uciconfig, ucintp, 'server_port'))) + uci.set(uciconfig, ucintp, 'server_port', '123'); + +if (isEmpty(uci.get(uciconfig, ucintp, 'interval'))) + uci.set(uciconfig, ucintp, 'interval', '30m'); + +/* cache file options were moved into a dedicated section */ +if (!uci.get(uciconfig, ucicache)) + uci.set(uciconfig, ucicache, uciconfig); + +if (isEmpty(uci.get(uciconfig, ucicache, 'enabled'))) + uci.set(uciconfig, ucicache, 'enabled', '1'); + +const old_cache_path = '/var/run/homeproxy/cache.db'; +const default_cache_path = '/etc/homeproxy/cache.db'; +const cache_path = uci.get(uciconfig, ucicache, 'path'); +if (isEmpty(cache_path) || cache_path === old_cache_path) + uci.set(uciconfig, ucicache, 'path', default_cache_path); + +if (!isEmpty(uci.get(uciconfig, ucicache, 'store_fakeip'))) + uci.delete(uciconfig, ucicache, 'store_fakeip'); + +if (isEmpty(uci.get(uciconfig, ucicache, 'store_rdrc'))) + uci.set(uciconfig, ucicache, 'store_rdrc', isEmpty(uci.get(uciconfig, ucidns, 'cache_file_store_rdrc')) ? '1' : uci.get(uciconfig, ucidns, 'cache_file_store_rdrc')); + +if (isEmpty(uci.get(uciconfig, ucicache, 'rdrc_timeout')) && !isEmpty(uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout'))) + uci.set(uciconfig, ucicache, 'rdrc_timeout', uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout')); + +if (!isEmpty(uci.get(uciconfig, ucidns, 'cache_file_store_rdrc'))) + uci.delete(uciconfig, ucidns, 'cache_file_store_rdrc'); + +if (!isEmpty(uci.get(uciconfig, ucidns, 'cache_file_rdrc_timeout'))) + uci.delete(uciconfig, ucidns, 'cache_file_rdrc_timeout'); + /* create migration section */ if (!uci.get(uciconfig, ucimigration)) uci.set(uciconfig, ucimigration, uciconfig); @@ -75,6 +188,10 @@ if (isEmpty(uci.get(uciconfig, ucimain, 'log_level'))) if (isEmpty(uci.get(uciconfig, uciserver, 'log_level'))) uci.set(uciconfig, uciserver, 'log_level', 'warn'); +if (!isEmpty(uci.get(uciconfig, 'subscription', 'auto_update_time')) && + !is_update_cron(uci.get(uciconfig, 'subscription', 'auto_update_time'))) + uci.set(uciconfig, 'subscription', 'auto_update_time', '0 0 * * *'); + /* empty value defaults to all ports now */ if (uci.get(uciconfig, ucimain, 'routing_port') === 'all') uci.delete(uciconfig, ucimain, 'routing_port'); @@ -239,6 +356,47 @@ uci.foreach(uciconfig, uciroutingrule, (cfg) => { } }); +/* routing node options */ +uci.foreach(uciconfig, uciroutingnode, (cfg) => { + if (cfg.node === 'urltest' && !isEmpty(cfg.urltest_nodes)) { + let custom_nodes = [], + subscription_nodes = []; + + for (let node in (type(cfg.urltest_nodes) === 'array' ? cfg.urltest_nodes : [ cfg.urltest_nodes ])) { + let node_cfg = uci.get_all(uciconfig, node); + if (isEmpty(node_cfg)) + continue; + + if (!isEmpty(node_cfg.grouphash)) + push(subscription_nodes, node); + else + push(custom_nodes, node); + } + + if (isEmpty(cfg.selected_nodes) && !isEmpty(custom_nodes)) + uci.set(uciconfig, cfg['.name'], 'selected_nodes', custom_nodes); + + if (isEmpty(cfg.subscription_nodes) && !isEmpty(subscription_nodes)) + uci.set(uciconfig, cfg['.name'], 'subscription_nodes', subscription_nodes); + + uci.delete(uciconfig, cfg['.name'], 'urltest_nodes'); + } +}); + +/* rule set options */ +uci.foreach(uciconfig, uciruleset, (cfg) => { + if (isEmpty(cfg.tag)) + uci.set(uciconfig, cfg['.name'], 'tag', 'cfg-' + cfg['.name'] + '-rule'); + + if (cfg.type === 'remote') { + if (isEmpty(cfg.auto_update)) + uci.set(uciconfig, cfg['.name'], 'auto_update', '1'); + + if (!is_update_cron(cfg.update_interval)) + uci.set(uciconfig, cfg['.name'], 'update_interval', '0 0 * * *'); + } +}); + /* server options */ /* auto_firewall was moved into server options */ const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall'); @@ -246,6 +404,13 @@ if (!isEmpty(auto_firewall)) uci.delete(uciconfig, uciserver, 'auto_firewall'); uci.foreach(uciconfig, uciserver, (cfg) => { + if (!isEmpty(cfg.hysteria_revc_window_client) && isEmpty(cfg.hysteria_recv_window_client)) { + uci.set(uciconfig, cfg['.name'], 'hysteria_recv_window_client', cfg.hysteria_revc_window_client); + } + + if (!isEmpty(cfg.hysteria_revc_window_client)) + uci.delete(uciconfig, cfg['.name'], 'hysteria_revc_window_client'); + /* auto_firewall was moved into server options */ if (auto_firewall === '1') uci.set(uciconfig, cfg['.name'], 'firewall' , '1'); diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_provider.sh b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_provider.sh new file mode 100644 index 000000000..76e2cfc9f --- /dev/null +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_provider.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +set -eu + +NAME="homeproxy" +RUN_DIR="/var/run/$NAME" +REQUEST_FILE="$RUN_DIR/provider-update-id" +LOCK_FILE="$RUN_DIR/provider-update-v2.lock" +LOG_FILE="$RUN_DIR/homeproxy.log" +PROVIDER_ID="${1:-}" + +log() { + printf '%s [SUBSCRIBE] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE" +} + +case "$PROVIDER_ID" in + *[!0-9a-f]*|'') exit 1 ;; +esac +[ "${#PROVIDER_ID}" -eq 32 ] || exit 1 + +mkdir -p "$RUN_DIR" +if ! lock -n "$LOCK_FILE" 2>/dev/null; then + log "Another provider update is running; waiting for it to finish..." + lock "$LOCK_FILE" +fi + +umask 077 +printf '%s\n' "$PROVIDER_ID" > "$REQUEST_FILE" +cleanup() { + rm -f "$REQUEST_FILE" + lock -u "$LOCK_FILE" 2>/dev/null || true +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +# Give the Clash API response time to reach the panel before HomeProxy restarts. +log "Updating provider $PROVIDER_ID from Clash API..." +sleep 1 +if /etc/homeproxy/scripts/update_subscriptions.uc; then + log "Provider $PROVIDER_ID update completed." +else + log "Provider $PROVIDER_ID update failed." + exit 4 +fi diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh index 8dd467bcc..83f49aef4 100755 --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh @@ -21,64 +21,65 @@ to_upper() { } check_list_update() { - local listtype="$1" - local listrepo="$2" - local listref="$3" - local listname="$4" - local lock="$RUN_DIR/update_resources-$listtype.lock" - local github_token="$(uci -q get homeproxy.config.github_token)" - local wget="wget --timeout=10 -q" + local LIST_FILE="$1" + local REPO_NAME="$2" + local REPO_BRANCH="$3" + local REPO_FILE="$4" + local LOCK_FILE="$RUN_DIR/update_resources-$LIST_FILE.lock" + local GITHUB_TOKEN="$(uci -q get homeproxy.config.github_token)" - exec 200>"$lock" - if ! flock -n 200 &> "/dev/null"; then - log "[$(to_upper "$listtype")] A task is already running." + if ! lock -n "$LOCK_FILE" 2>"/dev/null"; then + log "[$(to_upper "$LIST_FILE")] A task is already running." return 2 fi - [ -z "$github_token" ] || github_token="--header=Authorization: Bearer $github_token" - local list_info="$($wget "${github_token:--q}" -O- "https://api.github.com/repos/$listrepo/commits?sha=$listref&path=$listname&per_page=1")" - local list_sha="$(echo -e "$list_info" | jsonfilter -qe "@[0].sha")" - local list_ver="$(echo -e "$list_info" | jsonfilter -qe "@[0].commit.message" | grep -Eo "[0-9-]+" | tr -d '-')" - if [ -z "$list_sha" ] || [ -z "$list_ver" ]; then - log "[$(to_upper "$listtype")] Failed to get the latest version, please retry later." + local NEW_VER=$(curl -sL ${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} "https://api.github.com/repos/$REPO_NAME/releases/latest" | jsonfilter -e "@.tag_name") + if [ -z "$NEW_VER" ]; then + log "[$(to_upper "$LIST_FILE")] Failed to get the latest version, please retry later." + return 1 fi - local local_list_ver="$(cat "$RESOURCES_DIR/$listtype.ver" 2>"/dev/null" || echo "NOT FOUND")" - if [ "$local_list_ver" = "$list_ver" ]; then - log "[$(to_upper "$listtype")] Current version: $list_ver." - log "[$(to_upper "$listtype")] You're already at the latest version." + local OLD_VER=$(cat "$RESOURCES_DIR/$LIST_FILE.ver" 2>/dev/null || echo "NOT FOUND") + if [ "$OLD_VER" = "$NEW_VER" ]; then + log "[$(to_upper "$LIST_FILE")] Current version: $NEW_VER." + log "[$(to_upper "$LIST_FILE")] You're already at the latest version." + return 3 else - log "[$(to_upper "$listtype")] Local version: $local_list_ver, latest version: $list_ver." + log "[$(to_upper "$LIST_FILE")] Local version: $OLD_VER, latest version: $NEW_VER." fi - if ! $wget "https://fastly.jsdelivr.net/gh/$listrepo@$list_sha/$listname" -O "$RUN_DIR/$listname" || [ ! -s "$RUN_DIR/$listname" ]; then - rm -f "$RUN_DIR/$listname" - log "[$(to_upper "$listtype")] Update failed." + if ! curl -sL -o "$RUN_DIR/$REPO_FILE" "https://cdn.jsdelivr.net/gh/$REPO_NAME@$REPO_BRANCH/$REPO_FILE" || [ ! -s "$RUN_DIR/$REPO_FILE" ]; then + rm -f "$RUN_DIR/$REPO_FILE" + log "[$(to_upper "$LIST_FILE")] Update failed." + return 1 fi - mv -f "$RUN_DIR/$listname" "$RESOURCES_DIR/$listtype.${listname##*.}" - echo -e "$list_ver" > "$RESOURCES_DIR/$listtype.ver" - log "[$(to_upper "$listtype")] Successfully updated." + mv -f "$RUN_DIR/$REPO_FILE" "$RESOURCES_DIR/$LIST_FILE.${REPO_FILE##*.}" + echo -e "$NEW_VER" > "$RESOURCES_DIR/$LIST_FILE.ver" + log "[$(to_upper "$LIST_FILE")] Successfully updated." return 0 } case "$1" in "china_ip4") - check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv4.txt" + check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \ + sed -i "/IP-CIDR6,/d; s/IP-CIDR,//g" "$RESOURCES_DIR/china_ip4.txt" ;; "china_ip6") - check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv6.txt" + check_list_update "$1" "Loyalsoldier/surge-rules" "release" "cncidr.txt" && \ + sed -i "/IP-CIDR,/d; s/IP-CIDR6,//g" "$RESOURCES_DIR/china_ip6.txt" ;; "gfw_list") - check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "gfw.txt" + check_list_update "$1" "Loyalsoldier/surge-rules" "release" "gfw.txt" && \ + sed -i "s/^\.//g" "$RESOURCES_DIR/gfw_list.txt" ;; "china_list") - check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "direct-list.txt" && \ - sed -i -e "s/full://g" -e "/:/d" "$RESOURCES_DIR/china_list.txt" + check_list_update "$1" "Loyalsoldier/surge-rules" "release" "direct.txt" && \ + sed -i "s/^\.//g" "$RESOURCES_DIR/china_list.txt" ;; *) echo -e "Usage: $0 " diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_rulesets.sh b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_rulesets.sh new file mode 100644 index 000000000..efba96f51 --- /dev/null +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_rulesets.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2025 ImmortalWrt.org + +CACHE_PATH="$(uci -q get homeproxy.cache.path)" +[ -n "$CACHE_PATH" ] || CACHE_PATH="/etc/homeproxy/cache.db" + +rm -f "$CACHE_PATH" +/etc/init.d/homeproxy restart diff --git a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc index 020a91303..e824c1e03 100755 --- a/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc +++ b/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc @@ -8,7 +8,7 @@ 'use strict'; import { md5 } from 'digest'; -import { open } from 'fs'; +import { open, readfile } from 'fs'; import { connect } from 'ubus'; import { cursor } from 'uci'; @@ -16,7 +16,7 @@ import { urldecode, urlencode } from 'luci.http'; import { init_action } from 'luci.sys'; import { - wGET, decodeBase64Str, getTime, isEmpty, parseURL, + wGETResponse, wGETHeaders, decodeBase64Str, getTime, isEmpty, parseURL, validation, HP_DIR, RUN_DIR } from 'homeproxy'; @@ -34,9 +34,15 @@ const allow_insecure = uci.get(uciconfig, ucisubscription, 'allow_insecure') || filter_mode = uci.get(uciconfig, ucisubscription, 'filter_nodes') || 'disabled', filter_keywords = uci.get(uciconfig, ucisubscription, 'filter_keywords') || [], packet_encoding = uci.get(uciconfig, ucisubscription, 'packet_encoding') || 'xudp', - subscription_urls = uci.get(uciconfig, ucisubscription, 'subscription_url') || [], - user_agent = uci.get(uciconfig, ucisubscription, 'user_agent'), - via_proxy = uci.get(uciconfig, ucisubscription, 'update_via_proxy') || '0'; + all_subscription_urls = uci.get(uciconfig, ucisubscription, 'subscription_url') || [], + user_agent = uci.get(uciconfig, ucisubscription, 'user_agent'), + via_proxy = uci.get(uciconfig, ucisubscription, 'update_via_proxy') || '0'; + +const provider_update_id = trim(readfile(RUN_DIR + '/provider-update-id') || ''); +let subscription_urls = all_subscription_urls; +if (provider_update_id) + subscription_urls = filter(all_subscription_urls, (url) => + md5(replace(url, /#.*$/, '')) === provider_update_id); const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainalnd_china'; let main_node, main_udp_node; @@ -80,10 +86,751 @@ function log(...args) { logfile.close(); } +function has_value(value) { + return value !== null && value !== '' && value !== 'nil'; +} + +function to_string(value) { + return has_value(value) ? sprintf('%s', value) : null; +} + +function bool_to_uci(value) { + if (value === true) + return '1'; + if (value === false) + return '0'; + return null; +} + +function normalize_list(value) { + if (!has_value(value)) + return null; + if (type(value) === 'array') + return value; + return [to_string(value)]; +} + +function normalize_alpn(value) { + if (!has_value(value)) + return null; + if (type(value) === 'array') + return value; + if (type(value) === 'string') { + let items = map(split(value, ','), (v) => trim(v)); + items = filter(items, (v) => length(v)); + return length(items) ? items : null; + } + return [to_string(value)]; +} + +function normalize_host_list(value) { + if (!has_value(value)) + return null; + if (type(value) === 'array') + return value; + return split(to_string(value), ','); +} + +function normalize_first(value) { + if (!has_value(value)) + return null; + if (type(value) === 'array') + return length(value) ? value[0] : null; + return to_string(value); +} + +function normalize_hysteria_hopping_port(mport) { + if (!has_value(mport)) + return null; + + let ports = []; + for (let p in split(to_string(mport), ',')) { + p = trim(p); + if (!p) + continue; + if (match(p, /^\d+$/)) + p = p + ':' + p; + else + p = replace(p, '-', ':'); + push(ports, p); + } + + return length(ports) ? ports : null; +} + +function normalize_mihomo_ports(ports) { + if (!has_value(ports)) + return null; + + if (type(ports) === 'array') + return map(ports, (p) => { + const v = to_string(p); + if (match(v, /^\d+$/)) + return v + ':' + v; + return replace(v, '-', ':'); + }); + + return normalize_hysteria_hopping_port(to_string(ports)); +} + +function parse_mihomo_speed(value) { + if (!has_value(value)) + return null; + + const str = to_string(value); + const match_val = match(str, /[0-9]+(\.[0-9]+)?/); + if (!match_val) + return null; + + const num_str = type(match_val) === 'array' ? match_val[0] : match_val; + if (!num_str) + return null; + + const num = int(num_str); + if (num === null || num != num) + return null; + + return to_string(num); +} + +function get_header_host(headers) { + if (type(headers) !== 'object') + return null; + + return headers.Host || headers.host || headers['HOST']; +} + +function apply_transport_opts(config, proxy) { + const network = proxy.network; + if (!has_value(network) || network === 'tcp') + return; + + let ws_opts = proxy['ws-opts'] || {}; + let grpc_opts = proxy['grpc-opts'] || {}; + let http_opts = proxy['http-opts'] || proxy['h2-opts'] || {}; + let httpupgrade_opts = proxy['http-upgrade-opts'] || {}; + + switch (network) { + case 'ws': + config.transport = 'ws'; + config.ws_path = ws_opts.path ? to_string(ws_opts.path) : null; + config.ws_host = get_header_host(ws_opts.headers); + config.websocket_early_data = ws_opts['early-data'] ? to_string(ws_opts['early-data']) : null; + config.websocket_early_data_header = ws_opts['early-data-header-name'] ? + to_string(ws_opts['early-data-header-name']) : null; + break; + case 'grpc': + config.transport = 'grpc'; + config.grpc_servicename = to_string(grpc_opts['grpc-service-name'] || grpc_opts['service-name']); + break; + case 'http': + case 'h2': + config.transport = 'http'; + config.http_path = normalize_first(http_opts.path); + config.http_host = normalize_host_list(get_header_host(http_opts.headers) || http_opts.host); + break; + case 'httpupgrade': + config.transport = 'httpupgrade'; + config.httpupgrade_host = get_header_host(httpupgrade_opts.headers) || httpupgrade_opts.host; + config.http_path = normalize_first(httpupgrade_opts.path); + break; + } +} + +function parse_mihomo_proxy(proxy) { + if (type(proxy) !== 'object') + return null; + + let config; + const tls_sni = proxy.servername || proxy.sni; + const tls_fingerprint = proxy['client-fingerprint'] || proxy.fingerprint; + const tls_insecure = (proxy['skip-cert-verify'] === true || proxy.insecure === '1' || proxy.allowInsecure === true || proxy.allowInsecure === '1') ? '1' + : (proxy['skip-cert-verify'] === false || proxy.insecure === '0' || proxy.allowInsecure === false || proxy.allowInsecure === '0') ? '0' + : null; + + switch (proxy.type) { + case 'anytls': { + let anytls_fp = (proxy['client-fingerprint'] !== null && proxy['client-fingerprint'] !== undefined) ? + proxy['client-fingerprint'] : proxy.fingerprint; + anytls_fp = to_string(anytls_fp); + if (anytls_fp === 'none' || anytls_fp === 'disable' || anytls_fp === 'disabled') + anytls_fp = null; + else if (!has_value(anytls_fp)) + anytls_fp = 'chrome'; + config = { + label: proxy.name, + type: 'anytls', + address: proxy.server, + port: to_string(proxy.port), + password: proxy.password, + tls: '1', + tls_sni: tls_sni || proxy.peer, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tls_utls: sing_features.with_utls ? anytls_fp : null, + anytls_idle_session_check_interval: to_string(proxy['idle-session-check-interval']), + anytls_idle_session_timeout: to_string(proxy['idle-session-timeout']), + anytls_min_idle_session: to_string(proxy['min-idle-session']), + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + } + case 'vmess': + config = { + label: proxy.name, + type: 'vmess', + address: proxy.server, + port: to_string(proxy.port), + uuid: proxy.uuid, + vmess_alterid: has_value(proxy.alterId) ? to_string(proxy.alterId) : null, + vmess_encrypt: proxy.cipher, + packet_encoding: proxy['packet-encoding'], + tls: (proxy.tls === true) ? '1' : '0', + tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tls_utls: sing_features.with_utls ? tls_fingerprint : null, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + apply_transport_opts(config, proxy); + break; + case 'hysteria2': + if (!sing_features.with_quic) { + log(sprintf('Skipping unsupported %s node: %s.', proxy.type, proxy.name || proxy.server)); + log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); + return null; + } + config = { + label: proxy.name, + type: 'hysteria2', + address: proxy.server, + port: to_string(proxy.port), + password: proxy.password, + hysteria_hopping_port: normalize_mihomo_ports(proxy.ports), + hysteria_down_mbps: parse_mihomo_speed(proxy.down), + hysteria_up_mbps: parse_mihomo_speed(proxy.up), + hysteria_obfs_type: proxy.obfs, + hysteria_obfs_password: proxy['obfs-password'], + tls: '1', + tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + case 'hysteria': + if (!sing_features.with_quic) { + log(sprintf('Skipping unsupported %s node: %s.', proxy.type, proxy.name || proxy.server)); + log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); + return null; + } + config = { + label: proxy.name, + type: 'hysteria', + address: proxy.server, + port: to_string(proxy.port), + hysteria_hopping_port: normalize_mihomo_ports(proxy.ports), + hysteria_protocol: proxy.protocol, + hysteria_auth_type: proxy['auth-str'] ? 'string' : (proxy.auth ? 'base64' : null), + hysteria_auth_payload: proxy['auth-str'] || proxy.auth, + hysteria_obfs_password: proxy.obfs, + hysteria_down_mbps: parse_mihomo_speed(proxy.down), + hysteria_up_mbps: parse_mihomo_speed(proxy.up), + tls: '1', + tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + case 'vless': + config = { + label: proxy.name, + type: 'vless', + address: proxy.server, + port: to_string(proxy.port), + uuid: proxy.uuid, + vless_flow: proxy.flow, + packet_encoding: proxy['packet-encoding'], + tls: (proxy.tls === true || proxy['reality-opts']) ? '1' : '0', + tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tls_utls: sing_features.with_utls ? tls_fingerprint : null, + tls_reality: proxy['reality-opts'] ? '1' : '0', + tls_reality_public_key: proxy['reality-opts'] ? proxy['reality-opts']['public-key'] : null, + tls_reality_short_id: proxy['reality-opts'] ? proxy['reality-opts']['short-id'] : null, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + apply_transport_opts(config, proxy); + break; + case 'trojan': + config = { + label: proxy.name, + type: 'trojan', + address: proxy.server, + port: to_string(proxy.port), + password: proxy.password, + tls: (proxy.tls === false) ? '0' : '1', + tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tls_utls: sing_features.with_utls ? tls_fingerprint : null, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + apply_transport_opts(config, proxy); + break; + case 'ss': { + let ss_plugin = proxy.plugin; + if (ss_plugin === 'simple-obfs') + ss_plugin = 'obfs-local'; + config = { + label: proxy.name, + type: 'shadowsocks', + address: proxy.server, + port: to_string(proxy.port), + shadowsocks_encrypt_method: proxy.cipher, + password: proxy.password, + shadowsocks_plugin: ss_plugin, + shadowsocks_plugin_opts: proxy['plugin-opts'], + udp_over_tcp: bool_to_uci(proxy['udp-over-tcp']), + udp_over_tcp_version: to_string(proxy['udp-over-tcp-version']), + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + } + case 'ssr': + log(sprintf('Skipping unsupported ssr node: %s.', proxy.name || proxy.server)); + return null; + case 'socks5': + case 'socks': + case 'socks4': + case 'socks4a': + config = { + label: proxy.name, + type: 'socks', + address: proxy.server, + port: to_string(proxy.port), + username: proxy.username, + password: proxy.password, + socks_version: (proxy.type === 'socks4a') ? '4a' : ((proxy.type === 'socks4') ? '4' : '5'), + tls: (proxy.tls === true) ? '1' : '0', + tls_sni, + tls_insecure, + tls_utls: sing_features.with_utls ? tls_fingerprint : null, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + case 'http': + config = { + label: proxy.name, + type: 'http', + address: proxy.server, + port: to_string(proxy.port), + username: proxy.username, + password: proxy.password, + tls: (proxy.tls === true) ? '1' : '0', + tls_sni, + tls_insecure, + tls_utls: sing_features.with_utls ? tls_fingerprint : null, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + case 'tuic': { + if (!sing_features.with_quic) { + log(sprintf('Skipping unsupported %s node: %s.', proxy.type, proxy.name || proxy.server)); + log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); + return null; + } + let tuic_heartbeat = proxy['heartbeat-interval']; + if (has_value(tuic_heartbeat)) { + tuic_heartbeat = int(tuic_heartbeat); + if (tuic_heartbeat >= 1000) + tuic_heartbeat = int(tuic_heartbeat / 1000); + } + config = { + label: proxy.name, + type: 'tuic', + address: proxy.server, + port: to_string(proxy.port), + uuid: proxy.uuid, + password: proxy.password || proxy.token, + tuic_congestion_control: proxy['congestion-controller'], + tuic_udp_relay_mode: proxy['udp-relay-mode'], + tuic_udp_over_stream: bool_to_uci(proxy['udp-over-stream']), + tuic_enable_zero_rtt: bool_to_uci(proxy['zero-rtt-handshake']), + tuic_heartbeat: has_value(tuic_heartbeat) ? to_string(tuic_heartbeat) : null, + tls: '1', + tls_sni: proxy['disable-sni'] ? null : tls_sni, + tls_alpn: normalize_alpn(proxy.alpn), + tls_insecure, + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + } + case 'ssh': + config = { + label: proxy.name, + type: 'ssh', + address: proxy.server, + port: to_string(proxy.port), + username: proxy.username, + password: proxy.password, + ssh_client_version: proxy['client-version'], + ssh_host_key: normalize_list(proxy['host-key']), + ssh_host_key_algo: normalize_list(proxy['host-key-algorithms']), + ssh_priv_key: normalize_list(proxy['private-key']), + ssh_priv_key_pp: proxy['private-key-passphrase'], + tcp_fast_open: (proxy.tfo === true) ? '1' : null + }; + break; + case 'wireguard': { + let wg_addresses = []; + if (has_value(proxy.ip)) + push(wg_addresses, to_string(proxy.ip)); + if (has_value(proxy.ipv6)) + push(wg_addresses, to_string(proxy.ipv6)); + config = { + label: proxy.name, + type: 'wireguard', + address: proxy.server, + port: to_string(proxy.port), + wireguard_local_address: length(wg_addresses) ? wg_addresses : null, + wireguard_private_key: proxy['private-key'], + wireguard_peer_public_key: proxy['public-key'], + wireguard_pre_shared_key: proxy['pre-shared-key'], + wireguard_reserved: normalize_list(proxy.reserved), + wireguard_mtu: to_string(proxy.mtu), + wireguard_persistent_keepalive_interval: to_string(proxy['persistent-keepalive'] || proxy['persistent-keepalive-interval'] || proxy.keepalive) + }; + break; + } + default: + return null; + } + + return config; +} + +function parse_subscription_userinfo(headers) { + let header_value = null; + for (let line in split(headers || '', '\n')) { + const matched = match(line, /^\s*subscription-userinfo:\s*(.*)$/i); + if (matched) + header_value = trim(matched[1]); + } + + if (isEmpty(header_value)) + return null; + + let info = {}; + for (let item in split(header_value, ';')) { + const matched = match(trim(item), /^([a-z]+)\s*=\s*([0-9]+)$/i); + if (!matched) + continue; + + const value = int(matched[2]); + if (value < 0) + continue; + + if (match(matched[1], /^upload$/i)) + info.upload = value; + else if (match(matched[1], /^download$/i)) + info.download = value; + else if (match(matched[1], /^total$/i)) + info.total = value; + else if (match(matched[1], /^expire$/i)) + info.expire = value; + } + + return length(info) ? info : null; +} + +function subscription_info_option(url) { + url = replace(url || '', /#.*$/, ''); + return 'subinfo_' + substr(md5(url), 0, 16); +} + +function subscription_display_name(url) { + const names = normalize_list(uci.get(uciconfig, ucisubscription, 'subscription_name')) || []; + let index = 0; + for (let configured_url in all_subscription_urls) { + if (replace(configured_url, /#.*$/, '') === replace(url, /#.*$/, '')) { + const name = trim(names[index] || ''); + return name || substr(md5(replace(url, /#.*$/, '')), 0, 8); + } + index++; + } + return substr(md5(replace(url, /#.*$/, '')), 0, 8); +} + +function previous_subscription_info(info_option) { + const value = uci.get(uciconfig, ucisubscription, info_option); + if (isEmpty(value)) + return null; + + try { + const info = json(value); + if (type(info) === 'object' && (('upload' in info) || ('download' in info) || + ('total' in info) || ('expire' in info))) + return info; + } catch (e) {} + + return null; +} + +function merge_subscription_info(current, previous) { + if (!current) + return previous || null; + if (!previous) + return current; + + for (let field in ['upload', 'download', 'total', 'expire']) + if (!(field in current) && (field in previous)) + current[field] = previous[field]; + + return current; +} + +function cleanup_subscription_info() { + let active_options = {}; + for (let url in all_subscription_urls) + active_options[subscription_info_option(url)] = true; + + const subscription = uci.get_all(uciconfig, ucisubscription) || {}; + for (let option in keys(subscription)) + if (match(option, /^subinfo_/) && !active_options[option]) + uci.delete(uciconfig, ucisubscription, option); +} + +function apply_sing_box_tls(config, outbound) { + const tls = outbound.tls; + if (type(tls) !== 'object') + return; + + config.tls = (tls.enabled === false) ? '0' : '1'; + config.tls_sni = tls.server_name; + config.tls_insecure = bool_to_uci(tls.insecure); + config.tls_alpn = normalize_alpn(tls.alpn); + config.tls_utls = sing_features.with_utls ? tls.utls?.fingerprint : null; + config.tls_reality = tls.reality ? '1' : '0'; + config.tls_reality_public_key = tls.reality?.public_key; + config.tls_reality_short_id = tls.reality?.short_id; +} + +function apply_sing_box_transport(config, outbound) { + const transport = outbound.transport; + if (type(transport) !== 'object') + return; + + config.transport = transport.type; + switch (transport.type) { + case 'ws': + config.ws_path = transport.path; + config.ws_host = get_header_host(transport.headers); + config.websocket_early_data = to_string(transport.max_early_data); + config.websocket_early_data_header = transport.early_data_header_name; + break; + case 'grpc': + config.grpc_servicename = transport.service_name; + break; + case 'http': + config.http_path = normalize_first(transport.path); + config.http_host = normalize_host_list(get_header_host(transport.headers) || transport.host); + config.http_method = transport.method; + break; + case 'httpupgrade': + config.httpupgrade_host = get_header_host(transport.headers) || transport.host; + config.http_path = normalize_first(transport.path); + break; + } +} + +function parse_sing_box_outbound(outbound) { + if (type(outbound) !== 'object') + return null; + + let config; + const port = to_string(outbound.server_port || outbound.port); + + switch (outbound.type) { + case 'anytls': + config = { + label: outbound.tag, + type: 'anytls', + address: outbound.server, + port, + password: outbound.password, + tls: '1' + }; + break; + case 'vmess': + config = { + label: outbound.tag, + type: 'vmess', + address: outbound.server, + port, + uuid: outbound.uuid, + vmess_alterid: to_string(outbound.alter_id), + vmess_encrypt: outbound.security, + packet_encoding: outbound.packet_encoding + }; + break; + case 'vless': + config = { + label: outbound.tag, + type: 'vless', + address: outbound.server, + port, + uuid: outbound.uuid, + vless_flow: outbound.flow, + packet_encoding: outbound.packet_encoding + }; + break; + case 'trojan': + config = { + label: outbound.tag, + type: 'trojan', + address: outbound.server, + port, + password: outbound.password, + tls: '1' + }; + break; + case 'shadowsocks': + config = { + label: outbound.tag, + type: 'shadowsocks', + address: outbound.server, + port, + shadowsocks_encrypt_method: outbound.method, + password: outbound.password, + shadowsocks_plugin: outbound.plugin, + shadowsocks_plugin_opts: outbound.plugin_opts + }; + break; + case 'hysteria': + config = { + label: outbound.tag, + type: 'hysteria', + address: outbound.server, + port, + hysteria_hopping_port: outbound.server_ports, + hysteria_auth_type: outbound.auth_str ? 'string' : (outbound.auth ? 'base64' : null), + hysteria_auth_payload: outbound.auth_str || outbound.auth, + hysteria_obfs_password: outbound.obfs, + hysteria_down_mbps: to_string(outbound.down_mbps), + hysteria_up_mbps: to_string(outbound.up_mbps), + tls: '1' + }; + break; + case 'hysteria2': + config = { + label: outbound.tag, + type: 'hysteria2', + address: outbound.server, + port, + password: outbound.password, + hysteria_hopping_port: outbound.server_ports, + hysteria_down_mbps: to_string(outbound.down_mbps), + hysteria_up_mbps: to_string(outbound.up_mbps), + hysteria_obfs_type: outbound.obfs?.type, + hysteria_obfs_password: outbound.obfs?.password, + tls: '1' + }; + break; + case 'tuic': + config = { + label: outbound.tag, + type: 'tuic', + address: outbound.server, + port, + uuid: outbound.uuid, + password: outbound.password, + tuic_congestion_control: outbound.congestion_control, + tuic_udp_relay_mode: outbound.udp_relay_mode, + tuic_udp_over_stream: bool_to_uci(outbound.udp_over_stream), + tuic_enable_zero_rtt: bool_to_uci(outbound.zero_rtt_handshake), + tuic_heartbeat: to_string(outbound.heartbeat), + tls: '1' + }; + break; + case 'socks': + config = { + label: outbound.tag, + type: 'socks', + address: outbound.server, + port, + username: outbound.username, + password: outbound.password, + socks_version: outbound.version || '5' + }; + break; + case 'http': + config = { + label: outbound.tag, + type: 'http', + address: outbound.server, + port, + username: outbound.username, + password: outbound.password + }; + break; + case 'ssh': + config = { + label: outbound.tag, + type: 'ssh', + address: outbound.server, + port, + username: outbound.user || outbound.username, + password: outbound.password, + ssh_client_version: outbound.client_version, + ssh_host_key: normalize_list(outbound.host_key), + ssh_host_key_algo: normalize_list(outbound.host_key_algorithms), + ssh_priv_key: normalize_list(outbound.private_key), + ssh_priv_key_pp: outbound.private_key_passphrase + }; + break; + case 'wireguard': + config = { + label: outbound.tag, + type: 'wireguard', + address: outbound.server, + port, + wireguard_local_address: normalize_list(outbound.local_address || outbound.address), + wireguard_private_key: outbound.private_key, + wireguard_peer_public_key: outbound.peer_public_key, + wireguard_pre_shared_key: outbound.pre_shared_key, + wireguard_reserved: normalize_list(outbound.reserved), + wireguard_mtu: to_string(outbound.mtu), + wireguard_persistent_keepalive_interval: to_string(outbound.persistent_keepalive_interval) + }; + break; + default: + return null; + } + + if (!isEmpty(config)) { + apply_sing_box_tls(config, outbound); + apply_sing_box_transport(config, outbound); + config.tcp_fast_open = bool_to_uci(outbound.tcp_fast_open); + config.tcp_multi_path = bool_to_uci(outbound.tcp_multi_path); + config.udp_fragment = bool_to_uci(outbound.udp_fragment); + } + + return config; +} + function parse_uri(uri) { let config, url, params; if (type(uri) === 'object') { + if (uri.nodetype === 'mihomo') { + return parse_mihomo_proxy(uri); + } + if (uri.nodetype === 'sing-box') { + return parse_sing_box_outbound(uri); + } if (uri.nodetype === 'sip008') { /* https://shadowsocks.org/guide/sip008.html */ config = { @@ -101,23 +848,29 @@ function parse_uri(uri) { uri = split(trim(uri), '://'); switch (uri[0]) { - case 'anytls': - /* https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md */ - url = parseURL('http://' + uri[1]) || {}; - params = url.searchParams || {}; + case 'anytls': + /* https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md */ + url = parseURL('http://' + uri[1]) || {}; + params = url.searchParams || {}; + let anytls_fp = params.fp || params.fingerprint; + if (anytls_fp === 'none' || anytls_fp === 'disable' || anytls_fp === 'disabled') + anytls_fp = null; + else if (!has_value(anytls_fp)) + anytls_fp = 'chrome'; - config = { - label: url.hash ? urldecode(url.hash) : null, - type: 'anytls', - address: url.hostname, - port: url.port, - password: urldecode(url.username), - tls: '1', - tls_sni: params.sni, - tls_insecure: (params.insecure === '1') ? '1' : '0' - }; + config = { + label: url.hash ? urldecode(url.hash) : null, + type: 'anytls', + address: url.hostname, + port: url.port, + password: urldecode(url.username), + tls: '1', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', + tls_sni: params.sni, + tls_utls: sing_features.with_utls ? anytls_fp : null + }; - break; + break; case 'http': case 'https': url = parseURL('http://' + uri[1]) || {}; @@ -151,6 +904,7 @@ function parse_uri(uri) { type: 'hysteria', address: url.hostname, port: url.port, + hysteria_hopping_port: normalize_hysteria_hopping_port(params.mport), hysteria_protocol: params.protocol || 'udp', hysteria_auth_type: params.auth ? 'string' : null, hysteria_auth_payload: params.auth, @@ -158,7 +912,7 @@ function parse_uri(uri) { hysteria_down_mbps: params.downmbps, hysteria_up_mbps: params.upmbps, tls: '1', - tls_insecure: (params.insecure in ['true', '1']) ? '1' : '0', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', tls_sni: params.peer, tls_alpn: params.alpn }; @@ -184,10 +938,11 @@ function parse_uri(uri) { password: url.username ? ( urldecode(url.username + (url.password ? (':' + url.password) : '')) ) : null, + hysteria_hopping_port: normalize_hysteria_hopping_port(params.mport), hysteria_obfs_type: params.obfs, hysteria_obfs_password: params['obfs-password'], tls: '1', - tls_insecure: (params.insecure === '1') ? '1' : '0', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', tls_sni: params.sni }; @@ -270,6 +1025,7 @@ function parse_uri(uri) { password: urldecode(url.username), transport: (params.type !== 'tcp') ? params.type : null, tls: '1', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', tls_sni: params.sni }; switch(params.type) { @@ -310,6 +1066,7 @@ function parse_uri(uri) { tuic_congestion_control: params.congestion_control, tuic_udp_relay_mode: params.udp_relay_mode, tls: '1', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', tls_sni: params.sni, tls_alpn: params.alpn ? split(urldecode(params.alpn), ',') : null, }; @@ -340,6 +1097,7 @@ function parse_uri(uri) { uuid: url.username, transport: (params.type !== 'tcp') ? params.type : null, tls: (params.security in ['tls', 'xtls', 'reality']) ? '1' : '0', + tls_insecure: (params.insecure === '1' || params.allowInsecure === '1') ? '1' : '0', tls_sni: params.sni, tls_alpn: params.alpn ? split(urldecode(params.alpn), ',') : null, tls_reality: (params.security === 'reality') ? '1' : '0', @@ -473,8 +1231,189 @@ function parse_uri(uri) { return config; } +function split_top_level(str, separator) { + let items = [], current = '', quote = null, depth = 0; + + for (let i = 0; i < length(str); i++) { + const ch = substr(str, i, 1); + + if (quote) { + current += ch; + if (ch === quote) + quote = null; + continue; + } + + if (ch === '"' || ch === "'") { + quote = ch; + current += ch; + continue; + } + + if (ch === '[' || ch === '{') + depth++; + else if (ch === ']' || ch === '}') + depth--; + + if (ch === separator && depth === 0) { + push(items, trim(current)); + current = ''; + } else { + current += ch; + } + } + + if (length(trim(current))) + push(items, trim(current)); + + return items; +} + +function strip_yaml_quotes(value) { + value = trim(value); + if ((match(value, /^".*"$/) || match(value, /^'.*'$/)) && length(value) >= 2) + return substr(value, 1, length(value) - 2); + + return value; +} + +function parse_yaml_value(value) { + value = trim(value || ''); + if (value === '' || value === 'null' || value === '~') + return null; + + if (match(value, /^\[.*\]$/)) { + let arr = []; + const inner = substr(value, 1, length(value) - 2); + for (let item in split_top_level(inner, ',')) + push(arr, parse_yaml_value(item)); + return arr; + } + + if (match(value, /^\{.*\}$/)) + return parse_yaml_inline_object(value); + + value = strip_yaml_quotes(value); + if (value === 'true') + return true; + if (value === 'false') + return false; + + return value; +} + +function parse_yaml_key_value(line) { + const m = match(line, /^([^:]+):\s*(.*)$/); + if (!m) + return null; + + return { + key: strip_yaml_quotes(trim(m[1])), + value: m[2] + }; +} + +function parse_yaml_inline_object(text) { + let obj = {}; + text = trim(text); + text = substr(text, 1, length(text) - 2); + + for (let part in split_top_level(text, ',')) { + const kv = parse_yaml_key_value(part); + if (kv) + obj[kv.key] = parse_yaml_value(kv.value); + } + + return obj; +} + +function yaml_indent(line) { + const m = match(line, /^(\s*)/); + return m ? length(m[1]) : 0; +} + +function stack_pop(stack) { + let next = []; + for (let i = 0; i < length(stack) - 1; i++) + push(next, stack[i]); + return next; +} + +function parse_mihomo_yaml(text) { + if (isEmpty(text) || type(text) !== 'string') + return null; + + let in_proxies = false, proxies = [], current = null, stack = []; + + for (let raw_line in split(text, '\n')) { + raw_line = replace(raw_line, /\r$/, ''); + let line = trim(raw_line); + + if (!line || match(line, /^#/)) + continue; + + if (line === 'proxies:' || match(line, /^proxies:\s*$/)) { + in_proxies = true; + continue; + } + if (!in_proxies) + continue; + + if (yaml_indent(raw_line) === 0 && match(line, /^[A-Za-z0-9_-]+:\s*$/)) + break; + + if (match(line, /^-\s*/)) { + if (current) + push(proxies, current); + + current = {}; + stack = [{ indent: yaml_indent(raw_line), obj: current }]; + line = replace(line, /^-\s*/, ''); + + if (!line) + continue; + + if (match(line, /^\{.*\}$/)) { + current = parse_yaml_inline_object(line); + current.nodetype = 'mihomo'; + stack = [{ indent: yaml_indent(raw_line), obj: current }]; + continue; + } + } + + if (!current) + continue; + + const kv = parse_yaml_key_value(line); + if (!kv) + continue; + + const indent = yaml_indent(raw_line); + while (length(stack) > 1 && indent <= stack[length(stack) - 1].indent) + stack = stack_pop(stack); + + const parent = stack[length(stack) - 1].obj; + if (trim(kv.value) === '') { + parent[kv.key] = {}; + push(stack, { indent, obj: parent[kv.key] }); + } else { + parent[kv.key] = parse_yaml_value(kv.value); + } + } + + if (current) + push(proxies, current); + + for (let proxy in proxies) + proxy.nodetype = 'mihomo'; + + return length(proxies) ? proxies : null; +} + function main() { - if (via_proxy !== '1') { + cleanup_subscription_info(); + + if (via_proxy !== '1' && !provider_update_id) { log('Stopping service...'); init_action('homeproxy', 'stop'); } @@ -484,7 +1423,8 @@ function main() { const groupHash = md5(url); node_cache[groupHash] = {}; - const res = wGET(url, user_agent); + const response = wGETResponse(url, user_agent), + res = response?.body; if (isEmpty(res)) { log(sprintf('Failed to fetch resources from %s.', url)); continue; @@ -492,14 +1432,38 @@ function main() { let nodes; try { - nodes = json(res).servers || json(res); + const parsed = json(res); + if (type(parsed?.servers) === 'array') { + nodes = parsed.servers; - /* Shadowsocks SIP008 format */ - if (nodes[0].server && nodes[0].method) - map(nodes, (_, i) => nodes[i].nodetype = 'sip008'); + /* Shadowsocks SIP008 format */ + if (nodes[0].server && nodes[0].method) + map(nodes, (_, i) => nodes[i].nodetype = 'sip008'); + } else if (type(parsed?.proxies) === 'array') { + nodes = parsed.proxies; + map(nodes, (_, i) => nodes[i].nodetype = 'mihomo'); + } else if (type(parsed?.outbounds) === 'array') { + nodes = parsed.outbounds; + map(nodes, (_, i) => nodes[i].nodetype = 'sing-box'); + } else if (type(parsed) === 'array') { + nodes = parsed; + if (nodes[0]?.server && nodes[0]?.method) + map(nodes, (_, i) => nodes[i].nodetype = 'sip008'); + else if (nodes[0]?.type && nodes[0]?.server) + map(nodes, (_, i) => nodes[i].nodetype = 'sing-box'); + } else { + nodes = []; + } } catch(e) { - nodes = decodeBase64Str(res); - nodes = nodes ? split(trim(replace(nodes, / /g, '_')), '\n') : []; + nodes = parse_mihomo_yaml(res); + if (isEmpty(nodes)) { + nodes = decodeBase64Str(res); + const decoded_nodes = parse_mihomo_yaml(nodes); + if (!isEmpty(decoded_nodes)) + nodes = decoded_nodes; + else + nodes = nodes ? split(trim(replace(nodes, / /g, '_')), '\n') : []; + } } let count = 0; @@ -523,7 +1487,7 @@ function main() { else { if (config.tls === '1' && allow_insecure === '1') config.tls_insecure = '1'; - if (config.type in ['vless', 'vmess']) + if (config.type in ['vless', 'vmess'] && isEmpty(config.packet_encoding)) config.packet_encoding = packet_encoding; config.grouphash = groupHash; @@ -538,14 +1502,51 @@ function main() { if (count == 0) log(sprintf('No valid node found in %s.', url)); - else + else { + const info_option = subscription_info_option(url), + display_name = subscription_display_name(url); + const previous_info = previous_subscription_info(info_option); + let subscription_info = parse_subscription_userinfo(response.headers); + + if (!subscription_info || !('total' in subscription_info) || !('expire' in subscription_info)) { + const fallback_headers = wGETHeaders(url, 'clash.meta'); + const fallback_info = parse_subscription_userinfo(fallback_headers); + if (fallback_info) { + if (subscription_info) + log(sprintf('Supplemented subscription user info for %s with Clash Meta user agent.', display_name)); + else + log(sprintf('Fetched subscription user info for %s with Clash Meta user agent.', display_name)); + subscription_info = merge_subscription_info(subscription_info, fallback_info); + } + } + + if (!subscription_info) { + subscription_info = previous_info || {}; + if (length(subscription_info)) + log(sprintf('Subscription %s did not return user info; keeping the previous values.', display_name)); + else + log(sprintf('Subscription %s did not return Subscription-Userinfo.', display_name)); + } else { + if (!('expire' in subscription_info)) + log(sprintf('Subscription %s returned traffic data without an expire value%s.', display_name, + previous_info && ('expire' in previous_info) ? '; keeping the previous value' : '')); + if (!('total' in subscription_info)) + log(sprintf('Subscription %s returned user info without a total value%s.', display_name, + previous_info && ('total' in previous_info) ? '; keeping the previous value' : '')); + subscription_info = merge_subscription_info(subscription_info, previous_info); + } + + subscription_info.updated_at = getTime(); + uci.set(uciconfig, ucisubscription, info_option, sprintf('%.J', subscription_info)); + log(sprintf('Successfully fetched %s nodes of total %s from %s.', count, length(nodes), url)); + } } if (isEmpty(node_result)) { log('Failed to update subscriptions: no valid node found.'); - if (via_proxy !== '1') { + if (via_proxy !== '1' && !provider_update_id) { log('Starting service...'); init_action('homeproxy', 'start'); } @@ -559,6 +1560,10 @@ function main() { if (!cfg.grouphash) return null; + /* A provider update must not touch nodes from other subscriptions. */ + if (!(cfg.grouphash in node_cache)) + return null; + /* Empty object - failed to fetch nodes */ if (length(node_cache[cfg.grouphash]) === 0) return null; @@ -592,7 +1597,7 @@ function main() { }); uci.commit(uciconfig); - let need_restart = (via_proxy !== '1'); + let need_restart = (via_proxy !== '1') || !!provider_update_id; if (!isEmpty(main_node)) { const first_server = uci.get_first(uciconfig, ucinode); if (first_server) { @@ -655,9 +1660,15 @@ function main() { log('Successfully updated subscriptions.'); } +if (provider_update_id && isEmpty(subscription_urls)) { + log(sprintf('Provider %s does not match any subscription.', provider_update_id)); + exit(1); +} + if (!isEmpty(subscription_urls)) try { - call(main); + if (call(main) === false) + exit(1); } catch(e) { log('[FATAL ERROR] An error occurred during updating subscriptions:'); log(sprintf('%s: %s', e.type, e.message)); @@ -666,4 +1677,5 @@ if (!isEmpty(subscription_urls)) log('Restarting service...'); init_action('homeproxy', 'stop'); init_action('homeproxy', 'start'); + exit(1); } diff --git a/luci-app-homeproxy/root/etc/init.d/homeproxy b/luci-app-homeproxy/root/etc/init.d/homeproxy index 3167775f0..ae3e64721 100755 --- a/luci-app-homeproxy/root/etc/init.d/homeproxy +++ b/luci-app-homeproxy/root/etc/init.d/homeproxy @@ -27,13 +27,82 @@ log() { echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH" } +init_logs() { + mkdir -p "$RUN_DIR" + mkdir -p "$HP_DIR/run/ui" + touch "$LOG_PATH" "$RUN_DIR/sing-box-c.log" "$RUN_DIR/sing-box-s.log" + chown -R sing-box:sing-box "$RUN_DIR" 2>"/dev/null" + chown -R sing-box:sing-box "$HP_DIR/run" 2>"/dev/null" +} + +valid_update_cron() { + echo "$1" | grep -Eq '^([0-9]|[0-5][0-9]) ([0-9]|[01][0-9]|2[0-3]) \* \* ([0-7]|\*)$' +} + +has_ruleset_cron() { + printf '%s\n' "$ruleset_cron_list" | grep -Fxq -- "$1" +} + +add_ruleset_cron() { + local section="$1" + local enabled auto_update type update_time + + config_get_bool enabled "$section" "enabled" "0" + config_get_bool auto_update "$section" "auto_update" "1" + config_get type "$section" "type" "local" + config_get update_time "$section" "update_interval" "" + + [ "$enabled" = "1" ] || return + [ "$auto_update" = "1" ] || return + [ "$type" = "remote" ] || return + [ -n "$update_time" ] || return + valid_update_cron "$update_time" || return + has_ruleset_cron "$update_time" && return + + echo -e "$update_time sh $HP_DIR/scripts/update_rulesets.sh #${CONF}_autosetup" >> "/etc/crontabs/root" + ruleset_cron_list="${ruleset_cron_list} +${update_time}" + cron_reload="1" +} + +setup_auto_update_cron() { + local cron_reload="0" + mkdir -p "/etc/crontabs" + touch "/etc/crontabs/root" + grep -q "#${CONF}_autosetup" "/etc/crontabs/root" 2>"/dev/null" && cron_reload="1" + sed -i "/#${CONF}_autosetup/d" "/etc/crontabs/root" 2>"/dev/null" + + local auto_update auto_update_time + config_get_bool auto_update "subscription" "auto_update" "0" + if [ "$auto_update" = "1" ]; then + config_get auto_update_time "subscription" "auto_update_time" "0 0 * * *" + if valid_update_cron "$auto_update_time"; then + echo -e "$auto_update_time sh $HP_DIR/scripts/update_crond.sh #${CONF}_autosetup" >> "/etc/crontabs/root" + cron_reload="1" + fi + fi + + local ruleset_cron_list="" + config_foreach add_ruleset_cron "ruleset" + [ "$cron_reload" = "1" ] && /etc/init.d/cron restart +} + start_service() { + init_logs + config_load "$CONF" + setup_auto_update_cron local routing_mode proxy_mode config_get routing_mode "config" "routing_mode" "bypass_mainland_china" config_get proxy_mode "config" "proxy_mode" "redirect_tproxy" + local cache_path + config_get cache_path "cache" "path" "$HP_DIR/cache.db" + mkdir -p "$(dirname "$cache_path")" + [ -e "$cache_path" ] || touch "$cache_path" + chown sing-box:sing-box "$cache_path" 2>"/dev/null" + local outbound_node if [ "$routing_mode" != "custom" ]; then config_get outbound_node "config" "main_node" "nil" @@ -48,8 +117,6 @@ start_service() { return 1 fi - mkdir -p "$RUN_DIR" - if [ "$outbound_node" != "nil" ]; then # Generate/Validate client config ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH" @@ -62,16 +129,6 @@ start_service() { return 1 fi - # Auto update - local auto_update auto_update_time - config_get_bool auto_update "subscription" "auto_update" "0" - if [ "$auto_update" = "1" ]; then - config_get auto_update_time "subscription" "auto_update_time" "2" - sed -i "/#${CONF}_autosetup/d" "/etc/crontabs/root" 2>"/dev/null" - echo -e "0 $auto_update_time * * * $HP_DIR/scripts/update_crond.sh #${CONF}_autosetup" >> "/etc/crontabs/root" - /etc/init.d/cron restart - fi - # DNSMasq rules local ipv6_support dns_port config_get_bool ipv6_support "config" "ipv6_support" "0" @@ -104,8 +161,6 @@ start_service() { sed -r -e '/^\s*$/d' -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port\nnftset=\/\1\\/4#inet#fw4#homeproxy_wan_proxy_addr_v4$wan_nftset_v6/g" \ "$HP_DIR/resources/proxy_list.txt" > "$DNSMASQ_DIR/proxy_list.conf" fi - /etc/init.d/dnsmasq restart >"/dev/null" 2>&1 - # Setup routing table local table_mark config_get table_mark "infra" "table_mark" "100" @@ -156,7 +211,8 @@ start_service() { procd_add_jail "sing-box-c" log procfs procd_add_jail_mount "$RUN_DIR/sing-box-c.json" procd_add_jail_mount_rw "$RUN_DIR/sing-box-c.log" - [ "$routing_mode" != "bypass_mainland_china" ] || procd_add_jail_mount_rw "$RUN_DIR/cache.db" + procd_add_jail_mount_rw "$cache_path" + procd_add_jail_mount_rw "$HP_DIR/run/" procd_add_jail_mount "$HP_DIR/certs/" procd_add_jail_mount "/etc/ssl/" procd_add_jail_mount "/etc/localtime" @@ -226,19 +282,15 @@ start_service() { procd_close_instance case "$routing_mode" in - "bypass_mainland_china") - # Prepare cache db - [ -e "$RUN_DIR/cache.db" ] || touch "$RUN_DIR/cache.db" - ;; "custom") # Prepare ruleset directory [ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset" ;; esac - [ "$outbound_node" = "nil" ] || echo > "$RUN_DIR/sing-box-c.log" + [ "$outbound_node" = "nil" ] || : > "$RUN_DIR/sing-box-c.log" if [ "$server_enabled" = "1" ]; then - echo > "$RUN_DIR/sing-box-s.log" + : > "$RUN_DIR/sing-box-s.log" mkdir -p "$HP_DIR/certs" fi @@ -249,6 +301,7 @@ start_service() { ucode "$HP_DIR/scripts/firewall_pre.uc" [ "$outbound_node" = "nil" ] || utpl -S "$HP_DIR/scripts/firewall_post.ut" > "$RUN_DIR/fw4_post.nft" fw4 reload >"/dev/null" 2>&1 + /etc/init.d/dnsmasq restart >"/dev/null" 2>&1 log "sing-box $(sing-box version -n) started." } @@ -308,8 +361,8 @@ stop_service() { rm -rf "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" "$DNSMASQ_DIR" /etc/init.d/dnsmasq restart >"/dev/null" 2>&1 - rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-c.log" \ - "$RUN_DIR/sing-box-s.json" "$RUN_DIR/sing-box-s.log" + rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-s.json" + init_logs log "Service stopped." } diff --git a/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy b/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy old mode 100644 new mode 100755 index fb6ebb36a..8d8cbf913 --- a/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy +++ b/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy @@ -3,6 +3,36 @@ [ -f "/www/luci-static/resources/icons/loading.gif" ] && \ sed -i "s,/loading.svg,/loading.gif,g" "/www/luci-static/resources/view/homeproxy/status.js" +uci -q set luci.main.lang='zh_cn' +uci -q commit luci + +api_secret="$(uci -q get homeproxy.clash_api.secret)" +if [ -z "$api_secret" ] || [ "$api_secret" = "homeproxy" ]; then + api_secret="$(tr -dc 'A-Za-z0-9' < /dev/urandom 2>"/dev/null" | head -c 16)" + [ -n "$api_secret" ] || api_secret="$(awk 'BEGIN{srand(); printf "%06d", int(rand() * 1000000)}')" + uci -q set homeproxy.clash_api.secret="$api_secret" + uci -q commit homeproxy +fi + +legacy_panel_url="https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip" +legacy_panel_proxy_url="https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip" +old_panel_url="https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" +old_panel_proxy_url="https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" +default_panel_url="https://gh-proxy.com/https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" +panel_url="$(uci -q get homeproxy.clash_api.external_ui_download_url)" +if [ -z "$panel_url" ] || [ "$panel_url" = "$legacy_panel_url" ] || [ "$panel_url" = "$legacy_panel_proxy_url" ] || [ "$panel_url" = "$old_panel_proxy_url" ]; then + uci -q set homeproxy.clash_api.external_ui_download_url="$default_panel_url" + uci -q commit homeproxy +fi + +rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>"/dev/null" + +mkdir -p /var/run/homeproxy +touch /var/run/homeproxy/homeproxy.log \ + /var/run/homeproxy/sing-box-c.log \ + /var/run/homeproxy/sing-box-s.log +chown -R sing-box:sing-box /var/run/homeproxy 2>"/dev/null" + uci -q batch <<-EOF >"/dev/null" delete firewall.homeproxy_pre diff --git a/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration b/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration old mode 100644 new mode 100755 diff --git a/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json b/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json index b4b97ea84..d65e15e06 100644 --- a/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json +++ b/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json @@ -9,6 +9,7 @@ "/var/run/homeproxy/sing-box-s.log": [ "read" ] }, "ubus": { + "rc": [ "*" ], "service": [ "list" ], "luci.homeproxy": [ "*" ] }, @@ -16,7 +17,11 @@ }, "write": { "file": { - "/tmp/homeproxy_certificate.tmp": [ "write" ] + "/tmp/homeproxy_certificate.tmp": [ "write" ], + "/tmp/homeproxy_panel.tmp": [ "write" ] + }, + "ubus": { + "rc": [ "*" ] }, "uci": [ "homeproxy" ] } diff --git a/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy b/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy index c87a9544e..f0292ebac 100644 --- a/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy +++ b/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy @@ -8,6 +8,7 @@ 'use strict'; import { access, error, lstat, popen, readfile, writefile } from 'fs'; +import { cursor } from 'uci'; /* Kanged from ucode/luci */ function shellquote(s) { @@ -21,6 +22,72 @@ function hasKernelModule(kmod) { const HP_DIR = '/etc/homeproxy'; const RUN_DIR = '/var/run/homeproxy'; +function getClashAPIConfig(uci) { + const external_controller = trim(uci.get('homeproxy', 'clash_api', 'external_controller') || '127.0.0.1:9090'); + const secret = trim(uci.get('homeproxy', 'clash_api', 'secret') || ''); + const matched = match(external_controller, /:(\d+)$/); + const port = matched ? matched[1] : '9090'; + + return { + base_url: `http://127.0.0.1:${port}`, + secret + }; +} + +function readProcess(fd) { + let payload = '', line; + for (line = fd.read('line'); length(line); line = fd.read('line')) + payload += line; + fd.close(); + + return payload; +} + +function clashAPIRequest(api, method, path, timeout) { + const auth = api.secret ? `--header ${shellquote(`Authorization: Bearer ${api.secret}`)} ` : ''; + const post = (method === 'POST') ? "--post-data='' " : ''; + const fd = popen(`/usr/bin/wget -qO- -T ${timeout || 10} ${auth}${post}${shellquote(api.base_url + path)} 2>"/dev/null"`); + + if (!fd) + return null; + + return readProcess(fd); +} + +function getPanelDir(uci) { + return trim(uci.get('homeproxy', 'clash_api', 'external_ui') || `${HP_DIR}/run/ui`); +} + +function hasDirectoryContent(directory) { + return (system(`[ -d ${shellquote(directory)} ] && [ -n "$(ls -A ${shellquote(directory)} 2>/dev/null)" ]`) === 0); +} + +function preparePanelBackup(panel_dir, backup_dir) { + const parent_dir = replace(panel_dir, /\/[^/]+$/, '') || '/'; + + system(`rm -rf ${shellquote(backup_dir)}`); + system(`mkdir -p ${shellquote(parent_dir)}`); + + if (hasDirectoryContent(panel_dir)) { + if (system(`mv ${shellquote(panel_dir)} ${shellquote(backup_dir)}`) !== 0) + return false; + } + + return (system(`mkdir -p ${shellquote(panel_dir)}`) === 0); +} + +function restorePanelBackup(panel_dir, backup_dir) { + if (!access(backup_dir)) + return true; + + system(`rm -rf ${shellquote(panel_dir)}`); + return (system(`mv ${shellquote(backup_dir)} ${shellquote(panel_dir)}`) === 0); +} + +function cleanupPanelBackup(backup_dir) { + system(`rm -rf ${shellquote(backup_dir)}`); +} + const methods = { acllist_read: { args: { type: 'type' }, @@ -130,14 +197,1106 @@ const methods = { url = 'https://www.baidu.com'; break; case 'google': - url = 'https://www.google.com'; + url = 'https://www.google.com/generate_204'; + break; + case 'github': + url = 'https://github.com'; + break; + case 'youtube': + url = 'https://www.youtube.com'; break; default: return { result: false, error: 'illegal site' }; break; } - return { result: (system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100) === 0) }; + const fd = popen(`/usr/bin/curl --connect-timeout 3 --max-time 5 -o /dev/null -I -skL -w "%{http_code}:%{time_pretransfer}" ${shellquote(url)} 2>"/dev/null"`); + if (!fd) + return { result: false }; + + const output = trim(readProcess(fd) || ''); + const parts = split(output, ':'); + const code = int(parts[0] || 0); + const parsed = match(parts[1] || '', /^([0-9]+)(\.([0-9]+))?$/); + + if (code > 0 && parsed) { + const sec = int(parsed[1]); + const frac = ((parsed[3] || '') + '00000'); + const centims = sec * 100000 + int(substr(frac, 0, 5)); + + return { + result: true, + latency: sprintf('%d.%02d', int(centims / 100), centims % 100), + type: 'curl' + }; + } + + return { result: false }; + } + }, + + current_node_get: { + call: function() { + const uci = cursor(); + uci.load('homeproxy'); + + const formatNode = (section_id, fallback_name) => { + if (!section_id || section_id in ['nil', 'same']) + return { id: section_id, label: fallback_name || section_id, type: null }; + + const section = uci.get_all('homeproxy', section_id); + if (!section || section['.type'] !== 'node') + return { id: section_id, label: fallback_name || section_id, type: null }; + + const address = trim(section.override_address || section.address || ''); + const port = trim(section.override_port || section.port || ''); + const label = trim(section.label || '') || ((address && port) ? `${address}:${port}` : section_id); + return { + id: section_id, + label, + type: section.type || null + }; + }; + + const normalizeOutboundTag = (tag) => { + if (!tag) + return null; + + tag = trim(sprintf('%s', tag)); + tag = replace(tag, /[\r\n\t]+/g, ' '); + tag = replace(tag, /[\/\\?#%]+/g, '-'); + tag = replace(tag, /\s+/g, ' '); + + return tag || null; + }; + + const sectionDisplayName = (section) => { + if (!section) + return null; + + let label = normalizeOutboundTag(section.label); + if (label) + return label; + + if (section['.type'] === 'node') { + const address = trim(section.override_address || section.address || ''); + const port = trim(section.override_port || section.port || ''); + + if (address && port) + return normalizeOutboundTag(`${address}:${port}`); + } + + return null; + }; + + const buildOutboundTagMap = () => { + const reserved_tags = { + 'GLOBAL': true, + 'main-out': true, + 'main-udp-out': true, + 'direct-out': true, + 'block-out': true + }; + let tag_map = {}, + used_tags = {}; + + const register = (section_id, display_name) => { + if (!section_id || tag_map[section_id]) + return; + + const fallback_tag = `cfg-${section_id}-out`; + const base_tag = normalizeOutboundTag(display_name) || fallback_tag; + let tag = base_tag, + suffix = 2; + + while (reserved_tags[tag] || used_tags[tag]) { + tag = `${base_tag} (${suffix})`; + suffix++; + } + + tag_map[section_id] = tag; + used_tags[tag] = true; + }; + + uci.foreach('homeproxy', 'node', (section) => { + register(section['.name'], sectionDisplayName(section)); + }); + + uci.foreach('homeproxy', 'routing_node', (section) => { + if (section.enabled === '1') + register(section['.name'], sectionDisplayName(section)); + }); + + let reverse_map = {}; + for (let section_id in tag_map) + reverse_map[tag_map[section_id]] = section_id; + + return reverse_map; + }; + + const main_node = trim(uci.get('homeproxy', 'config', 'main_node') || 'nil'); + if (main_node !== 'urltest') + return { + mode: main_node, + active: formatNode(main_node, (main_node === 'nil') ? '禁用' : null), + error: null + }; + + const payload = clashAPIRequest(getClashAPIConfig(uci), 'GET', '/proxies/main-out', 3); + if (payload === null) + return { + mode: 'urltest', + active: { id: 'urltest', label: '自动选择', type: 'urltest' }, + error: 'clash_api_unavailable' + }; + + let data = null; + try { + data = json(payload); + } catch (e) {} + + const tag = trim(data?.now || ''); + if (!tag) + return { + mode: 'urltest', + active: { id: 'urltest', label: '自动选择', type: 'urltest' }, + error: 'missing_current_proxy' + }; + + const tag_map = buildOutboundTagMap(); + const matched = match(tag, /^cfg-(.*)-out$/); + const section_id = tag_map[tag] || (matched ? matched[1] : tag); + + return { + mode: 'urltest', + active: formatNode(section_id, tag), + error: null + }; + } + }, + clash_api_update_panel: { + call: function() { + const uci = cursor(); + uci.load('homeproxy'); + const panel_dir = getPanelDir(uci); + const backup_dir = `${panel_dir}.backup`; + + if (!preparePanelBackup(panel_dir, backup_dir)) + return { result: false, error: 'panel_backup_failed' }; + + const payload = clashAPIRequest(getClashAPIConfig(uci), 'POST', '/upgrade/ui', 120); + if (payload === null) { + restorePanelBackup(panel_dir, backup_dir); + return { result: false, error: 'clash_api_unavailable' }; + } + + let data = null; + try { + data = json(payload); + } catch (e) {} + + if (data?.status === 'ok') { + cleanupPanelBackup(backup_dir); + return { result: true }; + } + + if (!restorePanelBackup(panel_dir, backup_dir)) + return { result: false, error: 'panel_restore_failed' }; + + return { + result: false, + error: data?.message || data?.error || 'update_panel_failed' + }; + } + }, + clash_api_install_panel: { + call: function() { + const uci = cursor(); + uci.load('homeproxy'); + + const tmpzip = '/tmp/homeproxy_panel.tmp'; + const filestat = lstat(tmpzip); + if (!filestat || filestat.type !== 'file' || filestat.size <= 0) { + system(`rm -f ${shellquote(tmpzip)}`); + return { result: false, error: 'empty_panel_zip' }; + } + + if (system('command -v unzip >/dev/null 2>&1') !== 0) { + system(`rm -f ${shellquote(tmpzip)}`); + return { result: false, error: 'unzip_unavailable' }; + } + + const panel_dir = getPanelDir(uci); + const backup_dir = `${panel_dir}.backup`; + const extract_dir = '/tmp/homeproxy_panel_extract'; + const stage_dir = '/tmp/homeproxy_panel_stage'; + const install_cmd = ` + set -e + rm -rf ${shellquote(extract_dir)} ${shellquote(stage_dir)} + mkdir -p ${shellquote(extract_dir)} ${shellquote(stage_dir)} + unzip -oq ${shellquote(tmpzip)} -d ${shellquote(extract_dir)} >/dev/null + src_dir=${shellquote(extract_dir)} + for depth in 1 2 3; do + [ -f "$src_dir/index.html" ] && break + first_entry="" + entry_count=0 + for entry in "$src_dir"/* "$src_dir"/.[!.]* "$src_dir"/..?*; do + [ -e "$entry" ] || continue + case "$(basename "$entry")" in + __MACOSX|.DS_Store) continue ;; + esac + entry_count=$((entry_count + 1)) + [ -n "$first_entry" ] || first_entry="$entry" + done + if [ "$entry_count" -eq 1 ] && [ -d "$first_entry" ]; then + src_dir="$first_entry" + else + break + fi + done + copied=0 + for entry in "$src_dir"/* "$src_dir"/.[!.]* "$src_dir"/..?*; do + [ -e "$entry" ] || continue + cp -a "$entry" ${shellquote(stage_dir)}/ + copied=1 + done + [ "$copied" -eq 1 ] + `; + + if (system(install_cmd) !== 0) { + system(`rm -f ${shellquote(tmpzip)}`); + system(`rm -rf ${shellquote(extract_dir)} ${shellquote(stage_dir)}`); + return { result: false, error: 'invalid_panel_zip' }; + } + + if (!preparePanelBackup(panel_dir, backup_dir)) { + system(`rm -f ${shellquote(tmpzip)}`); + system(`rm -rf ${shellquote(extract_dir)} ${shellquote(stage_dir)}`); + return { result: false, error: 'panel_backup_failed' }; + } + + const replace_cmd = ` + set -e + rm -rf ${shellquote(panel_dir)} + mkdir -p ${shellquote(panel_dir)} + cp -a ${shellquote(stage_dir)}/. ${shellquote(panel_dir)}/ + chown -R sing-box:sing-box ${shellquote(panel_dir)} 2>/dev/null || true + `; + + let result = { result: true }; + if (system(replace_cmd) !== 0) { + if (!restorePanelBackup(panel_dir, backup_dir)) + result = { result: false, error: 'panel_restore_failed' }; + else + result = { result: false, error: 'panel_install_failed' }; + } else { + cleanupPanelBackup(backup_dir); + } + + system(`rm -f ${shellquote(tmpzip)}`); + system(`rm -rf ${shellquote(extract_dir)} ${shellquote(stage_dir)}`); + return result; + } + }, + + node_latency_test: { + args: { node: 'node', mode: 'mode' }, + call: function(req) { + const error_codes = { + invalid_node: 'invalid_node', + invalid_mode: 'invalid_mode', + mode_mismatch: 'mode_mismatch', + rp_invalid_outbound: 'rp_invalid_outbound', + rp_runtime_prepare_failed: 'rp_runtime_prepare_failed', + rp_runtime_launch_failed: 'rp_runtime_launch_failed', + rp_runtime_timeout: 'rp_runtime_timeout', + rp_delay_timeout: 'rp_delay_timeout', + rp_delay_failed: 'rp_delay_failed', + icmp_probe_unavailable: 'icmp_probe_unavailable', + icmp_timeout: 'icmp_timeout', + icmp_failed: 'icmp_failed', + probe_timeout: 'probe_timeout', + backend_execution_failed: 'backend_execution_failed', + probe_failed: 'probe_failed' + }; + const probe_timeout_s = 3; + const probe_timeout_ms = probe_timeout_s * 1000; + const probe_system_timeout_ms = probe_timeout_ms + 250; + const rp_probe_url = 'https://cp.cloudflare.com/generate_204'; + const rp_probe_url_encoded = 'https%3A%2F%2Fcp.cloudflare.com%2Fgenerate_204'; + const rp_delay_timeout_ms = 5000; + + const formatResult = (node, state, latency_ms, err, extras) => ({ + node, + state, + latency_ms, + measured_at: time(), + error: err, + icmp_latency_ms: (extras && extras.icmp_latency_ms != null) ? extras.icmp_latency_ms : null, + icmp_error: (extras && extras.icmp_error) ? extras.icmp_error : null, + rp_runtime: (extras && extras.rp_runtime) ? extras.rp_runtime : null + }); + + const strToBool = (str) => ((str === '1') || null); + const strToInt = (str) => (str != null && trim(sprintf('%s', str)) !== '') ? (int(str) || null) : null; + const strToTime = (str) => (str != null && trim(sprintf('%s', str)) !== '') ? (str + 's') : null; + + function removeBlankAttrs(res) { + let content; + + if (type(res) === 'object') { + content = {}; + map(keys(res), (k) => { + if (type(res[k]) in ['array', 'object']) + content[k] = removeBlankAttrs(res[k]); + else if (res[k] !== null && res[k] !== '') + content[k] = res[k]; + }); + } else if (type(res) === 'array') { + content = []; + map(res, (k) => { + if (type(k) in ['array', 'object']) + push(content, removeBlankAttrs(k)); + else if (k !== null && k !== '') + push(content, k); + }); + } else + return res; + + return content; + } + + const parse_port = (strport) => { + if (type(strport) !== 'array' || length(strport) === 0) + return null; + + let ports = []; + for (let i = 0; i < length(strport); i++) + push(ports, int(strport[i])); + + return ports; + }; + + const generate_endpoint = (node_cfg) => { + if (type(node_cfg) !== 'object' || length(node_cfg) === 0) + return null; + + return { + type: node_cfg.type, + tag: 'cfg-' + node_cfg['.name'] + '-out', + address: node_cfg.wireguard_local_address, + mtu: strToInt(node_cfg.wireguard_mtu), + private_key: node_cfg.wireguard_private_key, + peers: (node_cfg.type === 'wireguard') ? [ + { + address: node_cfg.address, + port: strToInt(node_cfg.port), + allowed_ips: [ + '0.0.0.0/0', + '::/0' + ], + persistent_keepalive_interval: strToInt(node_cfg.wireguard_persistent_keepalive_interval), + public_key: node_cfg.wireguard_peer_public_key, + pre_shared_key: node_cfg.wireguard_pre_shared_key, + reserved: parse_port(node_cfg.wireguard_reserved) + } + ] : null, + system: (node_cfg.type === 'wireguard') ? false : null, + tcp_fast_open: strToBool(node_cfg.tcp_fast_open), + tcp_multi_path: strToBool(node_cfg.tcp_multi_path), + udp_fragment: strToBool(node_cfg.udp_fragment) + }; + }; + + const generate_outbound = (node_cfg) => { + if (type(node_cfg) !== 'object' || length(node_cfg) === 0) + return null; + + return { + type: node_cfg.type, + tag: 'cfg-' + node_cfg['.name'] + '-out', + + server: (node_cfg.type !== 'direct') ? node_cfg.address : null, + server_port: (node_cfg.type !== 'direct') ? strToInt(node_cfg.port) : null, + server_ports: node_cfg.hysteria_hopping_port, + + username: (node_cfg.type !== 'ssh') ? node_cfg.username : null, + user: (node_cfg.type === 'ssh') ? node_cfg.username : null, + password: node_cfg.password, + + idle_session_check_interval: strToTime(node_cfg.anytls_idle_session_check_interval), + idle_session_timeout: strToTime(node_cfg.anytls_idle_session_timeout), + min_idle_session: strToInt(node_cfg.anytls_min_idle_session), + hop_interval: strToTime(node_cfg.hysteria_hop_interval), + up_mbps: strToInt(node_cfg.hysteria_up_mbps), + down_mbps: strToInt(node_cfg.hysteria_down_mbps), + obfs: node_cfg.hysteria_obfs_type ? { + type: node_cfg.hysteria_obfs_type, + password: node_cfg.hysteria_obfs_password + } : node_cfg.hysteria_obfs_password, + auth: (node_cfg.hysteria_auth_type === 'base64') ? node_cfg.hysteria_auth_payload : null, + auth_str: (node_cfg.hysteria_auth_type === 'string') ? node_cfg.hysteria_auth_payload : null, + recv_window_conn: strToInt(node_cfg.hysteria_recv_window_conn), + recv_window: strToInt(node_cfg.hysteria_revc_window), + disable_mtu_discovery: strToBool(node_cfg.hysteria_disable_mtu_discovery), + method: node_cfg.shadowsocks_encrypt_method, + plugin: node_cfg.shadowsocks_plugin, + plugin_opts: node_cfg.shadowsocks_plugin_opts, + version: (node_cfg.type === 'shadowtls') ? strToInt(node_cfg.shadowtls_version) : ((node_cfg.type === 'socks') ? node_cfg.socks_version : null), + client_version: node_cfg.ssh_client_version, + host_key: node_cfg.ssh_host_key, + host_key_algorithms: node_cfg.ssh_host_key_algo, + private_key: node_cfg.ssh_priv_key, + private_key_passphrase: node_cfg.ssh_priv_key_pp, + uuid: node_cfg.uuid, + congestion_control: node_cfg.tuic_congestion_control, + udp_relay_mode: node_cfg.tuic_udp_relay_mode, + udp_over_stream: strToBool(node_cfg.tuic_udp_over_stream), + zero_rtt_handshake: strToBool(node_cfg.tuic_enable_zero_rtt), + heartbeat: strToTime(node_cfg.tuic_heartbeat), + flow: node_cfg.vless_flow, + alter_id: strToInt(node_cfg.vmess_alterid), + security: node_cfg.vmess_encrypt, + global_padding: strToBool(node_cfg.vmess_global_padding), + authenticated_length: strToBool(node_cfg.vmess_authenticated_length), + packet_encoding: node_cfg.packet_encoding, + + multiplex: (node_cfg.multiplex === '1') ? { + enabled: true, + protocol: node_cfg.multiplex_protocol, + max_connections: strToInt(node_cfg.multiplex_max_connections), + min_streams: strToInt(node_cfg.multiplex_min_streams), + max_streams: strToInt(node_cfg.multiplex_max_streams), + padding: strToBool(node_cfg.multiplex_padding), + brutal: (node_cfg.multiplex_brutal === '1') ? { + enabled: true, + up_mbps: strToInt(node_cfg.multiplex_brutal_up), + down_mbps: strToInt(node_cfg.multiplex_brutal_down) + } : null + } : null, + tls: (node_cfg.tls === '1') ? { + enabled: true, + server_name: node_cfg.tls_sni, + insecure: strToBool(node_cfg.tls_insecure), + alpn: node_cfg.tls_alpn, + min_version: node_cfg.tls_min_version, + max_version: node_cfg.tls_max_version, + cipher_suites: node_cfg.tls_cipher_suites, + certificate_path: node_cfg.tls_cert_path, + ech: (node_cfg.tls_ech === '1') ? { + enabled: true, + config: node_cfg.tls_ech_config, + config_path: node_cfg.tls_ech_config_path + } : null, + utls: node_cfg.tls_utls ? { + enabled: true, + fingerprint: node_cfg.tls_utls + } : null, + reality: (node_cfg.tls_reality === '1') ? { + enabled: true, + public_key: node_cfg.tls_reality_public_key, + short_id: node_cfg.tls_reality_short_id + } : null + } : null, + transport: node_cfg.transport ? { + type: node_cfg.transport, + host: node_cfg.http_host || node_cfg.httpupgrade_host, + path: node_cfg.http_path || node_cfg.ws_path, + headers: node_cfg.ws_host ? { + Host: node_cfg.ws_host + } : null, + method: node_cfg.http_method, + max_early_data: strToInt(node_cfg.websocket_early_data), + early_data_header_name: node_cfg.websocket_early_data_header, + service_name: node_cfg.grpc_servicename, + idle_timeout: node_cfg.http_idle_timeout, + ping_timeout: node_cfg.http_ping_timeout, + permit_without_stream: strToBool(node_cfg.grpc_permit_without_stream) + } : null, + udp_over_tcp: (node_cfg.udp_over_tcp === '1') ? { + enabled: true, + version: strToInt(node_cfg.udp_over_tcp_version) + } : null, + tcp_fast_open: strToBool(node_cfg.tcp_fast_open), + tcp_multi_path: strToBool(node_cfg.tcp_multi_path), + udp_fragment: strToBool(node_cfg.udp_fragment) + }; + }; + + const isControllerPortBusy = (port) => { + const hex_port = sprintf('%04X', int(port)); + const tcp_v4 = readfile('/proc/net/tcp') || ''; + const tcp_v6 = readfile('/proc/net/tcp6') || ''; + + for (let line in split(tcp_v4 + '\n' + tcp_v6, /\n/)) { + const fields = split(trim(line), /\s+/); + if (length(fields) < 4) + continue; + + const local_addr = split(fields[1], ':'); + const state = fields[3]; + if (length(local_addr) !== 2) + continue; + + if (local_addr[1] === hex_port && state === '0A') + return true; + } + + return false; + }; + + const pickControllerPort = (seed) => { + const min_port = 19090; + const span = 100; + let hash_seed = 0; + + for (let i = 0; i < length(seed); i++) + hash_seed += ord(seed, i); + + const start = hash_seed % span; + for (let i = 0; i < span; i++) { + const candidate = min_port + ((start + i) % span); + if (candidate === 9090) + continue; + if (!isControllerPortBusy(candidate)) + return candidate; + } + + return null; + }; + + const listRuntimePidsByConfig = (config_path) => { + if (!config_path) + return []; + + const scan_cmd = sprintf( + '/bin/sh -c %s -- %s', + shellquote('cfg="$1"; for proc in /proc/[0-9]*; do [ -d "$proc" ] || continue; pid="${proc#/proc/}"; [ -r "$proc/cmdline" ] || continue; found_cfg=0; has_sig=0; p1=""; p2=""; set -f; for arg in $(tr "\\000" "\\n" <"$proc/cmdline" 2>/dev/null); do [ "$arg" = "$cfg" ] && found_cfg=1; [ "${p2##*/}" = "sing-box" ] && [ "$p1" = "run" ] && [ "$arg" = "--config" ] && has_sig=1; p2="$p1"; p1="$arg"; done; set +f; [ "$has_sig" -eq 1 ] && [ "$found_cfg" -eq 1 ] && printf "%s\\n" "$pid"; done'), + shellquote(config_path) + ); + + const fd = popen(scan_cmd); + if (!fd) + return []; + + let pids = []; + let line; + for (line = fd.read('line'); line !== null && line !== ''; line = fd.read('line')) { + const pid = trim(line); + if (match(pid, /^[0-9]+$/) && index(pids, pid) === -1) + push(pids, pid); + } + fd.close(); + + return pids; + }; + + const isRuntimeProcess = (pid, config_path) => { + if (!pid || !match(pid, /^[0-9]+$/) || !config_path) + return false; + + if (system(`/bin/kill -0 ${shellquote(pid)} >/dev/null 2>&1`) !== 0) + return false; + + return (index(listRuntimePidsByConfig(config_path), pid) !== -1); + }; + + const findRuntimePidByConfig = (config_path) => { + const pids = listRuntimePidsByConfig(config_path); + for (let i = 0; i < length(pids); i++) { + const pid = pids[i]; + if (system(`/bin/kill -0 ${shellquote(pid)} >/dev/null 2>&1`) === 0) + return pid; + } + + return null; + }; + + const resolveRuntimePid = (runtime_meta) => { + if (!runtime_meta?.config_path) + return null; + + const pid_file = trim(readfile(runtime_meta.pid_path) || ''); + if (isRuntimeProcess(pid_file, runtime_meta.config_path)) + return pid_file; + + const discovered = findRuntimePidByConfig(runtime_meta.config_path); + if (discovered) + writefile(runtime_meta.pid_path, discovered + '\n'); + + return discovered; + }; + + const cleanupProbeRuntime = (runtime_meta) => { + if (!runtime_meta) + return; + + const terminateRuntimePid = (pid) => { + if (!pid) + return; + + system(`/bin/kill ${shellquote(pid)} >/dev/null 2>&1`); + system('/bin/sleep 1'); + if (system(`/bin/kill -0 ${shellquote(pid)} >/dev/null 2>&1`) === 0) + system(`/bin/kill -9 ${shellquote(pid)} >/dev/null 2>&1`); + }; + + let pid = resolveRuntimePid(runtime_meta); + while (pid) { + terminateRuntimePid(pid); + system(`/bin/rm -f ${shellquote(runtime_meta.pid_path)}`); + pid = findRuntimePidByConfig(runtime_meta.config_path); + } + + system(`/bin/rm -rf ${shellquote(runtime_meta.session_dir)}`); + }; + + const waitControllerReady = (runtime_meta, timeout_ms) => { + try { + let retries = int((timeout_ms + 999) / 1000); + if (!retries || retries < 1) + retries = 1; + + for (let i = 0; i < retries; i++) { + resolveRuntimePid(runtime_meta); + + const probe_fd = popen(`/usr/bin/wget -qO- ${shellquote(runtime_meta.controller_url + '/version')} 2>"/dev/null"`); + if (probe_fd) { + let payload = '', line; + while (true) { + line = probe_fd.read('line'); + if (line === null || line === '') + break; + payload += line; + } + probe_fd.close(); + if (trim(payload)) + return true; + } + + if (i + 1 < retries) + system('/bin/sleep 1'); + } + + return false; + } catch (e) { + return false; + } + }; + + const buildProbeRuntime = (node_name, node_cfg) => { + const runtime_root = RUN_DIR + '/latency-test'; + const session_id = sprintf('%s-%d-%d', node_name, time(), int(readfile('/proc/uptime') || '0')); + const session_dir = `${runtime_root}/${session_id}`; + const config_path = `${session_dir}/sing-box.json`; + const log_path = `${session_dir}/sing-box.log`; + const cache_path = `${session_dir}/cache.db`; + const pid_path = `${session_dir}/sing-box.pid`; + + const controller_port = pickControllerPort(node_name || 'real-proxy'); + if (!controller_port) + return { error: 'no available temporary controller port' }; + + const selected_tag = 'probe-out'; + const config = { + log: { + disabled: false, + level: 'warn', + output: log_path, + timestamp: true + }, + outbounds: [ + { + type: 'direct', + tag: 'direct-out' + }, + { + type: 'block', + tag: 'block-out' + } + ], + endpoints: [], + route: { + final: selected_tag, + auto_detect_interface: true + }, + experimental: { + clash_api: { + external_controller: `127.0.0.1:${controller_port}` + }, + cache_file: { + enabled: true, + path: cache_path + } + } + }; + + const rendered_outbound = (node_cfg.type === 'wireguard') ? generate_endpoint(node_cfg) : generate_outbound(node_cfg); + if (rendered_outbound && type(rendered_outbound) === 'object') + rendered_outbound.tag = selected_tag; + + if (!rendered_outbound || type(rendered_outbound) !== 'object' || rendered_outbound.tag !== selected_tag || !rendered_outbound.type) + return { + error: 'failed to render temporary outbound for selected node', + code: error_codes.rp_invalid_outbound + }; + + if (node_cfg.type === 'wireguard') + push(config.endpoints, rendered_outbound); + else + push(config.outbounds, rendered_outbound); + + if (system(`/bin/mkdir -p ${shellquote(session_dir)}`) !== 0) + return { error: 'failed to prepare temporary runtime directory' }; + + if (!writefile(config_path, sprintf('%.J\n', removeBlankAttrs(config)))) { + system(`/bin/rm -rf ${shellquote(session_dir)}`); + return { error: 'failed to write temporary sing-box config' }; + } + + return { + runtime_root, + session_dir, + config_path, + log_path, + cache_path, + pid_path, + controller_port, + controller_url: `http://127.0.0.1:${controller_port}` + }; + }; + + const launchProbeRuntime = (runtime_meta) => { + if (!runtime_meta) + return false; + + const launch_cmd = sprintf( + '/usr/bin/sing-box run --config %s >/dev/null 2>&1 & pid=$!; [ -n "$pid" ] && printf "%%s\\n" "$pid" > %s', + shellquote(runtime_meta.config_path), + shellquote(runtime_meta.pid_path) + ); + + if (system(`/bin/sh -c ${shellquote(launch_cmd)}`) !== 0) + return false; + + for (let i = 0; i < 5; i++) { + const pid = resolveRuntimePid(runtime_meta); + if (pid) + return true; + + system('/bin/sleep 1'); + } + + return false; + }; + + const monotonicMs = () => { + const uptime = trim(readfile('/proc/uptime') || ''); + const parsed = match(uptime, /^([0-9]+)(\.([0-9]+))?/); + + if (!parsed) + return null; + + const sec = int(parsed[1]); + const frac = ((parsed[3] || '') + '000'); + const msec = int(substr(frac, 0, 3)); + + return sec * 1000 + msec; + }; + + const probeRealProxyDelay = (runtime_meta, node_name) => { + const selected_tag = 'probe-out'; + const delay_endpoint = sprintf( + '%s/proxies/%s/delay?url=%s&timeout=%d', + runtime_meta.controller_url, + selected_tag, + rp_probe_url_encoded, + rp_delay_timeout_ms + ); + + const started_ms = monotonicMs(); + const fd = popen(sprintf( + '/usr/bin/wget -qO- -T %d %s 2>"/dev/null"', + int((rp_delay_timeout_ms + 999) / 1000), + shellquote(delay_endpoint) + )); + if (!fd) + return { + state: 'timeout', + error: { + code: error_codes.rp_delay_timeout, + message: 'failed to open temporary Clash API delay endpoint' + } + }; + + let payload = '', line; + while (true) { + line = fd.read('line'); + if (line === null || line === '') + break; + payload += line; + } + fd.close(); + + const ended_ms = monotonicMs(); + if (!trim(payload || '')) { + if (started_ms !== null && ended_ms !== null && ended_ms - started_ms >= rp_delay_timeout_ms) + return { + state: 'timeout', + error: { + code: error_codes.rp_delay_timeout, + message: sprintf('temporary Clash API delay request timed out after %dms', rp_delay_timeout_ms) + } + }; + + return { + state: 'error', + error: { + code: error_codes.rp_delay_failed, + message: 'temporary Clash API delay response is empty' + } + }; + } + + let data = null; + try { + data = json(payload); + } catch (e) { + return { + state: 'error', + error: { + code: error_codes.rp_delay_failed, + message: 'temporary Clash API delay response is not valid JSON' + } + }; + } + + const delay_ms = int(data?.delay || '0'); + if (delay_ms > 0) + return { + state: 'success', + latency_ms: delay_ms + }; + + const failure_msg = trim(sprintf('%s', data?.message || data?.error || '')) || 'temporary Clash API delay request failed'; + if (match(failure_msg, /timeout/i) || (started_ms !== null && ended_ms !== null && ended_ms - started_ms >= rp_delay_timeout_ms)) + return { + state: 'timeout', + error: { + code: error_codes.rp_delay_timeout, + message: failure_msg + } + }; + + return { + state: 'error', + error: { + code: error_codes.rp_delay_failed, + message: failure_msg + } + }; + }; + + const normalizeLatencyMode = (m) => (m === 'tcp') ? 'icmp' : m; + const node = trim(req.args?.node || '') || null; + const requested_mode = trim(req.args?.mode || ''); + const mode = normalizeLatencyMode(requested_mode); + const allowed_modes = ['icmp', 'rp']; + const allowed_configured_modes = ['icmp', 'tcp', 'rp']; + if (!node) + return formatResult(null, 'error', null, { + code: error_codes.invalid_node, + message: 'missing node section id' + }); + + if (index(allowed_modes, mode) === -1) + return formatResult(node, 'error', null, { + code: error_codes.invalid_mode, + message: sprintf('invalid latency test mode: %s', mode || '(empty)') + }); + + const uci = cursor(); + uci.load('homeproxy'); + + const configured_mode_raw = trim(uci.get('homeproxy', 'subscription', 'latency_test_mode') || ''); + const configured_mode = normalizeLatencyMode(configured_mode_raw); + if (configured_mode_raw && index(allowed_configured_modes, configured_mode_raw) === -1) + return formatResult(node, 'error', null, { + code: error_codes.invalid_mode, + message: sprintf('configured latency test mode is invalid: %s', configured_mode_raw || '(empty)') + }); + + if (configured_mode && configured_mode !== mode) + return formatResult(node, 'error', null, { + code: error_codes.mode_mismatch, + message: sprintf('request mode %s does not match configured mode %s', mode, configured_mode) + }); + + const section = uci.get_all('homeproxy', node); + if (!section || section['.type'] !== 'node') + return formatResult(node, 'error', null, { + code: error_codes.invalid_node, + message: 'node section does not exist or is not a node section' + }); + + const address = trim(section.address || ''); + const port = int(section.port || '0'); + if (!address || port <= 0 || port > 65535) + return formatResult(node, 'error', null, { + code: error_codes.invalid_node, + message: 'node address or port is invalid' + }); + + if (mode === 'rp') { + let runtime_meta = null; + let runtime_active = false; + let runtime_info = { + runtime_root: RUN_DIR + '/latency-test', + session_dir: null, + config_path: null, + pid_path: null, + controller: null, + launched: false, + controller_ready: false, + cleaned_up: false + }; + + runtime_meta = buildProbeRuntime(node, section); + if (runtime_meta?.error) + runtime_info.cleaned_up = true; + + if (runtime_meta?.error) + return formatResult(node, 'error', null, { + code: runtime_meta.code || error_codes.rp_runtime_prepare_failed, + message: runtime_meta.error + }, { + rp_runtime: runtime_info + }); + + runtime_info.session_dir = runtime_meta.session_dir; + runtime_info.config_path = runtime_meta.config_path; + runtime_info.pid_path = runtime_meta.pid_path; + runtime_info.controller = `127.0.0.1:${runtime_meta.controller_port}`; + runtime_active = true; + + const finalizeRealProxyResult = (state, latency_ms, err) => { + if (runtime_active && runtime_meta) { + cleanupProbeRuntime(runtime_meta); + runtime_active = false; + runtime_info.cleaned_up = true; + } + + return formatResult(node, state, latency_ms, err, { + rp_runtime: runtime_info + }); + }; + + try { + if (!launchProbeRuntime(runtime_meta)) + return finalizeRealProxyResult('error', null, { + code: error_codes.rp_runtime_launch_failed, + message: 'failed to launch temporary sing-box runtime' + }); + + runtime_info.launched = true; + + if (!waitControllerReady(runtime_meta, rp_delay_timeout_ms)) + return finalizeRealProxyResult('timeout', null, { + code: error_codes.rp_runtime_timeout, + message: 'temporary sing-box runtime controller did not become ready in time' + }); + + runtime_info.controller_ready = true; + const delay_result = probeRealProxyDelay(runtime_meta, node); + if (delay_result.state === 'success' && delay_result.latency_ms > 0) + return finalizeRealProxyResult('success', delay_result.latency_ms, null); + + if (delay_result.state === 'timeout') + return finalizeRealProxyResult('timeout', null, delay_result.error || { + code: error_codes.rp_delay_timeout, + message: sprintf('temporary Clash API delay request timed out after %dms', rp_delay_timeout_ms) + }); + + return finalizeRealProxyResult('error', null, delay_result.error || { + code: error_codes.rp_delay_failed, + message: sprintf('temporary Clash API delay probe failed for %s', rp_probe_url) + }); + } catch (e) { + return finalizeRealProxyResult('error', null, { + code: error_codes.backend_execution_failed, + message: 'unexpected backend error while handling temporary rp runtime' + }); + } + } + + const ping = access('/bin/ping') ? '/bin/ping' : (access('/usr/bin/ping') ? '/usr/bin/ping' : null); + + const runProbe = (command, timeout_ms) => { + const started_ms = monotonicMs(); + const probe_exit = system(command, timeout_ms); + const ended_ms = monotonicMs(); + let latency_ms = null; + if (started_ms !== null && ended_ms !== null && ended_ms >= started_ms) + latency_ms = ended_ms - started_ms; + return { probe_exit, latency_ms }; + }; + + let icmp_latency_ms = null; + let icmp_error = null; + + if (!ping) { + icmp_error = { + code: error_codes.icmp_probe_unavailable, + message: 'missing ping executable for ICMP probe' + }; + } + else { + const result = runProbe( + `${ping} -c 1 -W ${probe_timeout_s} ${shellquote(address)} >/dev/null 2>/dev/null`, + probe_system_timeout_ms + ); + + if (result.probe_exit === 0) + icmp_latency_ms = (result.latency_ms !== null) ? result.latency_ms : 0; + else if (result.probe_exit === -1 || (result.latency_ms !== null && result.latency_ms >= probe_timeout_ms)) + icmp_error = { + code: error_codes.icmp_timeout, + message: `ICMP ping timeout after ${probe_timeout_s}s` + }; + else if (result.probe_exit === 127) + icmp_error = { + code: error_codes.backend_execution_failed, + message: 'ICMP ping command execution failed' + }; + else + icmp_error = { + code: error_codes.icmp_failed, + message: `ICMP ping failed with exit code ${result.probe_exit}` + }; + } + + if (icmp_latency_ms !== null) + return formatResult(node, 'success', icmp_latency_ms, null, { + icmp_latency_ms, + icmp_error + }); + + if (icmp_error?.code === error_codes.icmp_timeout) + return formatResult(node, 'timeout', null, { + code: error_codes.probe_timeout, + message: `ICMP probe timed out after ${probe_timeout_s}s` + }, { + icmp_latency_ms, + icmp_error + }); + + return formatResult(node, 'error', null, icmp_error || { + code: error_codes.probe_failed, + message: 'ICMP probe failed' + }, { + icmp_latency_ms, + icmp_error + }); } }, @@ -150,6 +1309,10 @@ const methods = { const filestat = lstat(`${RUN_DIR}/${req.args?.type}.log`); if (filestat) writefile(`${RUN_DIR}/${req.args?.type}.log`, ''); + else { + system(`mkdir -p ${RUN_DIR}`); + writefile(`${RUN_DIR}/${req.args?.type}.log`, ''); + } return { result: true }; } }, @@ -231,6 +1394,60 @@ const methods = { } }, + package_get_version: { + call: function() { + let version = trim(readfile('/usr/share/homeproxy/version') || ''); + + if (!version) { + const status = readfile('/usr/lib/opkg/status'); + let in_package = false; + + if (status) { + const lines = split(status, '\n'); + for (let i = 0; i < length(lines); i++) { + let line = lines[i]; + line = trim(line); + + if (!line) { + in_package = false; + continue; + } + + if (line == 'Package: luci-app-homeproxy') { + in_package = true; + continue; + } + + if (in_package) { + let matched = match(line, /^Version: (.*)/); + if (matched) { + version = matched[1]; + break; + } + } + } + } + } + + if (!version) { + const fd = popen('/usr/bin/opkg status luci-app-homeproxy 2>/dev/null'); + if (fd) { + for (let line = fd.read('line'); length(line); line = fd.read('line')) { + let matched = match(trim(line), /^Version: (.*)/); + if (matched) { + version = matched[1]; + break; + } + } + + fd.close(); + } + } + + return { version: version || '-' }; + } + }, + resources_get_version: { args: { type: 'type' }, call: function(req) { diff --git a/luci-app-pingpacket/README.md b/luci-app-pingpacket/README.md new file mode 100644 index 000000000..1d77e17cd --- /dev/null +++ b/luci-app-pingpacket/README.md @@ -0,0 +1,183 @@ +# luci-app-pingpacket + +`luci-app-pingpacket` 是一个面向 OpenWrt 的 LuCI 状态监控插件,用于持续观察国内直连质量和国外经代理访问质量,并提供统计、日志和快速排障能力。 + +## 界面截图 + + + + + + + + + +
+ Ping丢包监控总览 +
+ 监控总览 +
+ Ping丢包监控设置 +
+ 监控设置 +
+ Ping丢包监控运行日志 +
+ 运行日志 +
+ +## 适用平台 + +- OpenWrt 24.10.x,发布 `ipk` 安装包 +- OpenWrt 25.12.x,发布 `apk` 安装包 +- 已安装 LuCI Web 管理界面 +- 设备需具备 `ping`、`curl`、`awk`、`sed`、`grep`、`tail` 等基础命令 + +当前仓库的 GitHub Actions 工作流默认构建 `x86/64` SDK 版本;插件本身为 LuCI 页面和 shell 脚本,原则上也可移植到其他支持 LuCI 的 OpenWrt 架构。 + +## 页面位置 + +后台路径: + +`状态 -> Ping丢包监控` + +子页面: + +- `监控` +- `日志` + +## 主要功能 + +### 监控页 + +- 支持分别配置国内目标和国外测速 URL +- 国内目标通过本机 `ping` 进行 ICMP 探测 +- 国外目标通过 `curl` + `socks5h` 代理进行连续测速,更接近代理实际访问质量 +- 国外测速仅将 HTTP `200/204` 视为成功 +- 页面内可直接启用或关闭监控服务 +- 点击“保存并应用”后会保存配置;如果当前已启用监控,服务会自动重启并按新配置重新开始累计统计 +- 当检测到“数据停滞”时,页面会直接显示“重新启动服务”按钮 +- 实时显示开始时间、运行时长、最近更新时间、CPU 占用、内存占用和服务状态 + +### 统计卡片 + +国内和国外分别独立统计以下数据: + +- 平均延迟 +- 最低延迟 +- 最高延迟 +- 丢包率 +- 丢包数 +- 样本数 + +统计口径说明: + +- 以上数据均为“本次服务启用后的累计统计值” +- 停止服务、重新启用服务或重启服务后,统计会重新开始 +- 国内和国外分别独立累计,不合并计算 + +### 日志页 + +- 显示插件运行日志 +- 顶部支持“自动刷新”开关,默认勾选 +- 支持“清除日志”按钮 +- 显示日志最后更新时间、日志行数和日志大小 +- 默认保留最近 500 行日志 + +当前日志会记录: + +- 服务启动 +- 服务停止 +- 单次丢包事件 +- 恢复事件 +- 国外代理测速失败原因 + +日志规则: + +- 只要单次探测失败,就立即记录一条日志 +- 连续失败时,每次失败都会单独记录 +- 故障后的首次成功响应会记录恢复日志 + +## 当前探测方式 + +### 国内 + +- 目标类型:IP 或域名 +- 探测方式:ICMP `ping` +- 默认目标:`www.baidu.com` +- 默认 Ping 间隔:`1` 秒 + +### 国外 + +- 目标类型:域名或完整 URL +- 探测方式:通过 `socks5h` 代理发起 `curl` 请求 +- 默认测速 URL:`https://www.google.com/generate_204` +- 默认代理端口:`7891` +- 默认测速间隔:`5` 秒 +- 代理主机固定为 OpenWrt 本机 `127.0.0.1` + +## 配置规则 + +- 国内和国外目标可以只填其中一个 +- 两个目标都为空时,不能“保存并应用” +- 两个目标都为空时,也不能通过开关启用服务 +- 若填写国外测速 URL,则必须同时填写代理端口 +- 代理端口必须为 `1-65535` 的数字 +- 外国测速 URL 未填写协议时,系统会自动按 `https://` 处理 +- 页面轮询刷新时,不会覆盖用户正在编辑但尚未保存的输入内容 + +## 默认配置 + +安装或补齐配置时,默认值如下: + +- 国内目标:`www.baidu.com` +- 国外测速 URL:`https://www.google.com/generate_204` +- 国外代理类型:`socks5h` +- 国外代理端口:`7891` +- 国内 Ping 间隔:`1` +- 国外测速间隔:`5` + +默认 UCI 配置示例: + +```uci +config pingpacket 'config' + option enabled '0' + option domestic_target 'www.baidu.com' + option foreign_target 'https://www.google.com/generate_204' + option foreign_proxy_type 'socks5h' + option foreign_proxy_host '127.0.0.1' + option foreign_proxy_port '7891' + option domestic_interval '1' + option foreign_interval '5' +``` + +## 依赖 + +- `luci-base` +- `luci-lua-runtime` +- `luci-compat` +- `curl` + +## 安装 + +### OpenWrt 24.10.x + +上传 `ipk` 安装包后执行: + +```sh +opkg install luci-app-pingpacket_v1.0.22_all.ipk +``` + +### OpenWrt 25.12.x + +上传 `apk` 安装包后执行: + +```sh +apk add --allow-untrusted ./luci-app-pingpacket_v1.0.22_all.apk +``` + +说明: + +- 升级时会尽量保留已有 `/etc/config/pingpacket` +- 若系统中存在历史遗留的 `/etc/config/pingpacket-opkg`,安装脚本会尽量兼容处理 + diff --git a/luci-app-pingpacket/jpg/1.png b/luci-app-pingpacket/jpg/1.png new file mode 100644 index 0000000000000000000000000000000000000000..cb170364b874945ca8c956cea9405a120c948f91 GIT binary patch literal 144913 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 z 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 UWXBCq^*QpRsj7!5L)(V^KXO~w{Qv*} literal 0 HcmV?d00001 diff --git a/luci-app-pingpacket/jpg/3.png b/luci-app-pingpacket/jpg/3.png new file mode 100644 index 0000000000000000000000000000000000000000..225792fbc92c1c17d9ced36fff45408e9df81512 GIT binary patch literal 121449 zcmbTdWl)@J(=CiMxVyvP?g4@ZhhV|o-QAtw?h-t>ySsaE*Welw+|H19Kj+zR)%pH> zzourY?wPq-R$sk(b-03@1QI+xJQx@ll9Z&V5*Qd18WJ?I78Nl8Kk ztZItz7z~UQOiEN(*!LSwX}?if^h9-^G%gRt-G4djhTux z78J9b6=R^p0v$2U4UJ~?BBXHT*uDlsL=zpIV~}>ff7hFzD|h>fbwaxF?~l_62M^ww zdal&$iFOGa6K>Q}8wn=#Cjl=f`ERklH@}kms&(B8Vv^$#;NTZdFikiDcN~RCNl{S& z04OL32=D-Zp?|%_lE?-9L_^WJ4Cn;KO;nd3AQ*D~R2GS*hRtJs7F~u%y@9Tv`W% zI%1_$5^o2r2a*YHERH69AzRn8v+SfDGdbMG%9Tv|Q)%q5{s{pRJHJJzEV$SF^E28@ z3SmjBV7lFpa%h9ZQpWAiOri48y&{tZ5S#kpm>QY&O!}AA^>BxQo+O>6e+owu@m)d~ zleHq&+@Si|<%=uI1dd;1x=SpcSz9e0@efs*(#uZ0d%IrKiP7ui$eLK70i~nb3<*Fw zqgTmakw<~(?V{N~QLJVIefdvn%Wp#1I?T-1>azJgSv!@e4{unu6X#08ezW;Sa8vkk z)aES$%V8Ht;I#gfvsXuz5q}L0N2;RLmGr_a;tVl%*U?%uJFl=tnJXWj5mfe(AoMCa zE??Hos8udL0w{V}Jcah^&r8V4`yChM5xeMJ?IVT@M16EOJ^{Mh@6xl}_>o}pOP(*D zRmMLQy5xy>#ZM#zk9FS7QI|$$z&{**uH+U&#_&L#PUuQzi19E}*@<|{zh3+UHDslx z0oU7{p>I^B<3oxn4OOUOt6=4{2z?eB_--r8)ksZ#`7RHodO36e*5HsKL{^wMhVAfD zn2G{bOA!+epMfVsK`&t=c~ft5pir2P7zBFll+qmLSPD6(;r>CcvOKBzsiBM|i&$&V z7^e31wlw<~h4n(bzD}ob^@*yK66Cp?r`ioaIt8cl9JPqo&!ISY0myN~Xsg-HgG2v$ z((7e*ey^5ld`EFH((OGVBbIZD0W%tg~>HGqTI}j82|w zU)qg-V&?>Ll;O~NtaW>poi<9M=ul58gfen3xt2wrEMo1u^oObEk`tNH(oO&Gn4XM; z04T^Tb3JkBj;7=nr(*b5o=khDmfH?`I)%ZC>y$CZ!EqPuK-NG>9unv8NWy9q^V9zB{zX?brfro?V5Eef46;HU^ zM^}hYoq5fB&?Gb{c1%MR#ToM;*c$!AMO1?|{s9etmaD%v zPH|?WW@1aWh?WOOf(8Qj- z-Y}C-;*Q#Q!3PR~Z_Zphpocxwlyf5t!;BZyv2Mg}gY~0G4OIl|YI<1^R%?^I=l1%j z__b#eo6}=cWLb8)GEU1rJ1lm}I~`9Bj`TJF1LQNEiNBl3#N%IAh>>_0ZB&B{0^Pb! zPZ6$9imE=K&HLrkP9lA2fN2)#2Gf$XveGBuJcA^ab6b_62A$;8!0QJFLDslH2FTv~ zEEcBjcxg>zaZzAk(bibaBBO4bc0OT!BK85Z4g}7+PpIdyNFH=;o{TR>5(Xla2sD); zmbsQ4T_-I(9-t`x5Y6-vtl7ipN8@8WB2*w&3{Q*YY&wHs1KpFE%~YGe$``S|DXJ_4 z)$PuGRIo2`WkOhtyv2l{6+hkrKb3qC9!O+Qz<<^WfIaxibQCFyx;~IwC;g2our9v{ChIu0VI z+>A_LrJ$#(>~1%VmEHC_wDNR1dgU1%=b9L4!1D_GwpBKe*}$F>ZMI7c2>QBy5uqE%Uee^{Jx zsFfh`z}sYzqJ&1`0Zr-nn^Mt0dv3Vfery`#u{c)fWE)SUPIrZ9e!-IfbOisb`FGd>OGC4>&2Dq}2GcDj)onkGee!~ll%3&kMJIF*741?rQeEO`m{ifE9PA^+@&mkw3D zUZ1}Jwf1AZh0#(8lWDmjPN`OdvqqIw?F(9!yE~!*3RH!q>+e*hS!p%PpCqFUQYDx` z3j^veav|ogo{HyUa9nccH?)$;@3pb|?6a894-e@aOkOU9aKi*U&R%L%z!Z|Xbu}G# zQ6tYOgeyz3l3rPj)4FtZF6`UK5*FEqW0jo*tQ!gEQ2j7cLp@<>xM+io{fx8l8A-nB z9{qZ9w1M2=d-%_`*nvQ7(hN`YUq$X>8XPa06>3=s2k?b32eAQvX0R&X^Lz6Vzr2nr z>|WA^?S|yH0D>qw*`eh#%2FrEfMe-ak~KuSXw4CYB4GsuB;hC?B@B6~Fh6;N0!oau z0}9k19s^((qK}LV{UJ^s1A%;GFh3e@K#!ck{Y{BhJPw~K;}}9uV$^Ih{(}UY4Ei-> zOd@Tu2!~&1$!-2mK1ov1SV2ubjfmM~!-OFXMAeqQJIXDF+=?2bne951q?h6$f4oVx z74E-fu1%Zv<~uwrMDIa52>g(m`@gTGR|vH&)pp6m06O9jq;m~s2jQg$w)0aT)W68r5JhBFLxZ?5W-r` z@UoUP)jV|?b)!jqDJ(NDBi^mC_P8``eo=yHI^7 z9)pN|ijRF=_tDMbj3`-CCrkH`FDH(yYSrbCQNNGL1c*_F*7|hVC ze%~z=k1F3JaDfrQ7Ro#kt|*vyE}En$*j8MaMo7mO>()t?D)bpnLDcWkw7$ksiCkkr zMjQOBg)Y3XM3gEeD#4R+TbsK$==#js$;&n?Id3AWD@|ULi08|CEk8Jk`Q@-yBD*?O zFkj+S90Ylpln5!1v!hX8$TD(tmAw?v*-&X158x~#O4Wx9*hJNIm6Wb4PnMw|@{}R* z*UHDDElj4Qmyz<7sHCx~Bs+4{PI?7V=;o33N|tNWd=5!*$-|b{zNaYQHeU_LAflR+ zNP{tUO{3>%pNrXvf18BBBbA7QXYlY(>#|b;&sUH`?)brcP&G_)LQt*n;@PM6s}R0a zbuD*{OaF7ADu1sb6>8LZ6rqFp$?ZNn=Y0J42l|fJ@X!SOwlPwl4d-3(l%xF)lF-9w zmbn7LZ}NkZJ#*+BUs=BjxT=_hoKcEr*UCbPlX`QwF%fe7p_^=9-ldJ^RS^;Tzh?&< zwjFK1@?B{!nBIwpcBa>WffkSs8)Q^yi6#6iYSJIJ#mYdh?| z_|gX#-WbflJ5o;_seSTTDbvHn+qx{(Q>H`$nKxXhC+Lzc4j(QccNmr$^~&>zujN*b ztRQz;!qEie_QzjgL^q}1#0QA^g0Cbo7&BOrnJi+jJc*Nh2fv5>x^3)VbW?J=NSV_?Os_!- z$x+Ghm^(sK1BWN7V#2*3bB=x*ACDhgy{q*7q=9xd0}(|_DQG2;3u9vM69sC1Pr_>R zPu?e9 zS(E)pX{D7na8_VP12UcxAC|TO!HpNK+W%l5iDkaA1;%%*pD(v%Q$cM1PYht*LBUr) zHbkTJAmB4x=h+x`gc)4lz@1=i#)JxF?U8~6zG$tweBpP3@5p04T`_g~T3T&E!Tafs zt@@PPt!kw3;6uN9HBo>9;A$D#kV|+ydsKcwLKY7hA42#z)}Jy&bc9T@LdN$v;<;?5 zQ7VDQpuHNK?274PnCwBB+-txaKB((0-CJZ0ny<1SvDV|X4qA_)(rlQ|wX=hp;laf!??_(xSu7Yk@SQeqX4^cU5M9Q1e*6>suKH4qro zQt&7^`LVr01PHlL;r#~^)4+m73LC;_;X6KxEF|A;XToCX`jn>$jS zO!RM_wAUZBfy19%j*`=#xq%c(2?{q4AFk-q88hY?s7M#Sdmfa@&u}^O5%4BJ(msnn z;)<8tZ&XDzu-T zxNWHzrSVXS(TF~%Po9-dqoPx}z0Fi3oGCKwv>eCBUlCCfbzwfk$J%U9!ds5S8Tx18 zh}JuPxC#+4^vyj&mr=0By5HwgNaBNF)uL#!T4uq75G3%a*|sKIX&>J1)eCa z>0F-*k(x{J-1gDq87nQMvhD34Y2)Wa(3WhAHew#B?EYj}TU1(a^3voKSMgC?0HOkZ z-l{aIL=&)}0*Y4nS}hg=v)2~O+Y-j4vAH2nOV&YEkpcq=ZSd^eh?4FTX&eMwv=ttj zf-y2n8mRNJ=JrzQ={bBVl9YQkECsAa39}hgx`7L(DGtL1mxEMD|6!|zvbffA|MH-i+xD=9DJNgNlpanWqGRPW%R0kDY zALf(lsW`9YKXD4wVpIV|kRe0XUb*8q3Kytvqh-{9r~|K;o^;|-IeC?zB@zw2$m7@v zkMe>o{7`F|X}A@Bn$k%(9pyv+o}+6ocVVhdi!S!C=79v`0H6zN37)Y=ccu|uCW3N+ zh-imm?Yld)4}E1@1H@yATR_!>@v75;b1al&W7%%(5t3oe$?J%u#uU`3P$~{06a@Gu zqqkDw!#Xw#(9Q4@I_>u^<(=7B+KxKP7f=$MJ&tDJcnPkEA^$g{CtBjrhd})3^bnQ8WbOtzhd@NMN-17+8c9s5~yey5Z={-;ZrBLF_#+ zic~_cFlfYCuI!0c1a>8>>SziRQ%|25?#D=jQrZO-fdV_5po%VJf+v={)ZtLAS_Sb`Yx#8L|U^ zZ-@mbW?CH7OOH-;>mS%`piNhwAaB(08{&U^uKp$A(C|&2ajwmm%{2mdKz$Kc0q;b3 zFuA-2h^DN!@SEy)j8Wzit(C~gA`A3Z5t$OABc&e_3Yv4GfQM_8GoS4kq5CPJNDS2i z^l(R8j4=1@J!@mLg+|@Wth>?CT!7n1?%4u6}w8-%G`E8a1?FFZ#%6d z$<4n}EeyT-L?n}fF3g75NOq)4`B;A{8%}27uXEj1ap>KYPt+%>k{P4a!&V;@4DeGqJcUTi*W%qLPL2pG5Ne*$$`pYzR zI8baniogedNyU)3_p|QyruJ0G%wYai)Ohu4UIGd}{HNoyh_)QGlYD4=P6+6Yi&61xdiBBSr7mdHZ!CHaU4SQ}Gd) zgeuR-4fu4iWnk|@TuR&X% z=GtED4*p=dP>+;_vf-z{$7b!E8!;UXTqcnJ3d{tIxKqjLykcuu|wkv<( z61%4gijn(;_0)-GP`2p6+R!V5U1m0+t5Q`4+wjvq5LWox>8g0XU#-g&GdL^}32bV> z8*ID48_ZV`GdOJ^KHsl;s06@2&@D_`u1sO}2fr|*Vxt(=K* zbRb&cNi`v#U+PEhE_b8~#3n=Jt=Qw+qz3V7JiMe>PHg+2D@8TVf^Je;&Q-&zc_HD8 z6NBktSqK_}A|Hu0Nn*J7!i%u!6;fZ$@~{wTE_+2Cgl}6*$za)=I^5cm<~yfm%T7^_ z?|C&H1i!M6J(;Vz7W7JJlZNVjZ72Nwo_#4%D+?NZg{y*wBUZqyUURW-*_IC z-X9-d9l2K^5aBy5T*e51O-KOpXqQ3aQZH#iIU#o>^9(f*=~QZdX&*2 z;;@(pWN_Ice$M3hdXvEsAV%L695S9FJ|JRS)~{j?2tMLrGyAb?E+0%()|w@(<%Nh{ z&W)*)W}E^PZMH~o{;!#?nqepH=xHV12qQIGO@B}3(!Ks9WzI_9BC<<7f`hZn=W9y)w|s+F0- z822!$Oy7{EJFY&gU*|ycOm_3`!GFjj>i&bopv6LT;rmn3-O#|X@^|=E&$O%m=K&@9 z&7f0p0jTL(s~;~m2EXQ?wwLwG?>yN((ifPAvwBBN)RX{0EnGUX%=SOz)X#lp}me0Wg zx}4_5can?0_AkuMgSoz1Fdr>-LRzmv=`Yl!v)qydcFn=`z-f)_r*#oHJ~j#d{McfV zTEyae7R_=7$@GTNavfNZ3+u+M!cIU;>`MJiSvD;;Q@p@uf5pGYM)6JFANCf!<@Z6$ z6j~vFTC8B0kM=8El+Ouk%jw?tufzc^pCYGzDymq68cKArW6mZPLo(@Ry97~0RZvt^ z{}UA;MBzsTAxe)t4o;)`e=G1pm#50Ha5^lOp_s;P@1o7BcxsC84OaBWZTOE*fo|CO z!uhzi)2bpyEq$+0=ww@DF7`%~xd^s3j%~V1rTTEydb%Ph!{ckWUiIFjXg~2`Z${0Y$Old)fiOf2wYN8e$<%~`jDgeCmt)Y8(gjq5mG(9;?wY5!Kg zvqHicaEg8{{6w>IAc^bp%yl*T`>t2TPN?v*?{Yfe)b@q1g8_S$`G*?@!rA`82KHl? z8yn$F8}UK+6Zg^0KvW6N(i-j%j)Zv>lN|2v54SPw7mQvAVc9+GDZ5!!C1_8pOn@i9_IOP*h;*3$#=$?C$q9Me z9g8y&%G|A)%v)(Qy%LEnMnHd|e$0>70;@%c+?=zDX3EbFU*#L=Q#-9~)xJh+KdyUY ze|g1v<-Y3~BdWh-JnJt)ezpW*TKP(B-F^mrzu@y0F#?+|YTo{~VsAJ6z_L9Z+pD~c z?(22Ca)ptGa-KOQS7n3X(UuENNroh8QK_>g0ZY&S}5wDzoz#Wj6K6}@t^`>`9bTwuvrTJqae`LNC~Lv z$38b$8B6~03actO-L>5m4ij$J!mkAvy+ikaAqlmqd!UbKg}jzabV~VWTjLI( zsl@34r$NjdyD)JfKm=RAb!5#*8?%H`Z*)Q1;=866(Yj^YNpRZRc3h?-n9v^o_IND` za@e!E*<1dpMzzHk^V&_%tDTNkb8(Rk>!z$c;@mGe@U|5+_nWspPCH$d0zn9`*O(o8 zCG&Fj`yrt{j~FZHfw&ON=>Uc=27S%;LugvP6Nh%8UDb>Mdf&3AMg19L{JXOjiFLd8 zU0WKVpS%d%-%8*5Ttn>;NS@EdTWnu@uMW4YkuEo!=%Xa-@En06r;OXnL2`k5jV}P0 zmj~8%S*Vz4SBZZ9VQc7|?&lAPIrj!x7I}|iz&@oGxlgEnHt+dcu68)>cRcudarUZ0 zUmYC99@!HBmEZRf*}`i!6z!*fObk=&-vfhUY2gtJi(F$PEEUP+VtH z>C=0jP75RvZ|MODEAOB#FPJ>>%6#tX4eXr1>DKmm>x9Vy;&M6g)hem$iHK^GNvvt>wRh6^jtsp6jWL5;!pIA#wA@Un0ZS?cn%ZW2&*C7bbn)00}R8} z$1PQ#qG)s%Hj?6*NfB{T4xDODbyqld3D#N%4@DF7`k75n;?YDUPU8#_(7ToyQw8eZ zoI$qt|r6hvU!bi_vNJ`}#+bU+UWW0>0( zHNU$>ZE=0k>+R%Y2)43KM{NJ*gZ+G8&$pu{gAz#H&JcQPhmY`s3qFJ&tt-Gp&l}3m z@1&ZASB_%Dw@4PMRE*b+51eVWp2Ax34Z7p4o7><|WOdK!m@E`lcnv+`<2l)k>!XhS zl+$?L60;wqgdab$@9UKnkDGKnfB`S~p=J2CHkPF8`_&IyRDw`{36t#v*Le=F*28T_$4`4*C_a#ElfUfwa&R>` zmdqKtb_S;O0I$CX9ar{{EF05!n?UgJhFKQY8o4zLoG&?%PDWy~4zj!WX2nx6-SY0D zopcG+A&po*RgC7R+w=54Rs)2s#38Ra+5jL#sqp9rnN@tc@Sb(+1@9Mg41+n-NS7#4 zeFFN|68R7=)ugy+b~IDUbb#X;X*!^+2*_wAc`LhILx9Nt8+G|)j(R6VRWOJI!Hb+_ z;a3*`W4l!~v=3Y+C+wD2--`m#`Zv>h#~HP~_?+783u>BoMmUSD53c7y*NpX8<;>H9 zd+Dc(VhXf|%OtL=y>D)TiEgLxIvFo0U$;JTbpwS{!)Nx5@7Dqd`Vb22f-+4Z8#Yck za2Gm>hn|1w;!=Dl1frdQx$^Bo;I1EZJ`Y7WQi+tAeYyWN#n(UD?i#=}DG3~f>gX!E3;PlVe^JN(|Mmo<(NV+To&ILs_dlO8L6^?fO0*!AyHKySo@bF;UN8&n zOF~3aNDKsYm%*FYG>4&#Ev|c}CfiL2Qxq@T)~A9dS({ zN2@s1)i8c%thqc~HQox*Ue>;ivyTDzHa^V59etNi7bKQ?gC=@sm!CD8g5M^;efjXp zdZJ~`9z0#JkBCK%rtBhjC)z^4f38M<%y+T@iD^|ev@1YEY(@byBptC?+j_}B1Y-lUBC@<_-b4`kd)-u zLu$Vub0O9&h1qWvl&M?&6(eyr|071ijanQa>9@)~c>T$W1QnPUn+GBy6!=&^PpxLT zfM<{#Xdo*}Kzsi#noT5^w=@=N-n3>{uXbQpf4xx?tr+h%Jo*|j=VP)0ZxHkpE{4(q zQ;i-VI^&G;`6|>uC)p99$@v5_e49@)>gKp%cWH9J-gW*D#ThHAWbnE|5AhN%TzG0R zzsi265tn*3o_FT~L0Y4IJV)(k@^TG?m7$1jK@4Gyf_uZmmuWdUGu)i4sTwgy-hQg9 z(Q>2QY5I&gJY`(<1OS*;A6{`beD`u4UWcIAd*o`?`=GS-a_RX2nvuInh{Mln_ef#` zi5U<2D6G4o0!T;fD()fg&sVyGZ4t5qv;AV_oh1F(!gDRo!@`a>33c5sSX=y7Q?Fk5 zePKH9ETOA6-K>%{;slVBfPK35FPFE0ZBib6+a(yD{XD0iL)b69Xg7?=B*??M8a5HC z1j}J7xX%?^T-TqjZcw~>217w3wJAb`Vt%Zg>lxSSFFVW|8T|-*#Gy~cmHwA*^4=Y9 zw*wNI5}eWo1-+}He|>bJiCcp@c&txwvt#bb741E>V`>SdO#fN{)5u8FNomCsPo~Q@CEEM zK;qY9#N9Tp@6SDXxnS5^=oa=-(XfH0WfJi}>j?PWqf$^81Zy}uI5oq&W=`YSF?_8k zS^+mOwq5vOYImI8<=NDW*I4dD zr12h;*xwkZq?2MQ#{quKhE(bGpHmP^$~T35{48gcmc`(ppV|O~pn!Azfcr=^1MOq>(rK{CZ+uaX)2ektuq6*Dc)FQc8vt5eUTn=dkujb_R1Zhxt z(rNs6vi-38J1!gMFeb%)#09ZjV2zM-KPx6RaB?4nzd0yB37<5!{dHkb&F68|n*FZ? z{kzO+%C(c$vSi8Cq+F)V1SNW)IUZN0*Nogbv@6k%4U$Hphq5Vx7nk}s*T()~x1v~8 z5{&{I{-b*ZtBlls>DEmYAcV_v=|0PPJ*_0wif7OPi`}u(z2RUgm=a95AG+`{*@BIC zu7*?`NWxh$VHxsGmIo{U;Z&>$W+X$ z@wrGX@v-c`3bSW6&HodJ%i{%Q>zJW4m{=u-rj5u=n-z>4R`Y4T(QQG^1Y zbtDIXsq{9GI$^U2H z<=o;nQF^&B=U?3X$CVF@AcfEFO8TF2R{g)z-goFc@&A1# zS^0knx-MbE{WJApX0^&dm92`rMjbI9wvTuC6E)&sCHMjv2iHmAai7Z;Tx%?qsPE=Y zC(lY5+aH5c*-;`5J!C2T{-p0ayFrHQ`??%GhDQ}Mb`9#rE%`ULJ-xI5b+d1^`)(8@ zfah4YcFcAp%&}54&lA~hE;9`dM(TE*MORzXzlZt7&wC`HTzNkoZoQ@4_RUMtz7(kx zWf_QByzBQ!ma_iq@cTr6Rf=xmgyJ%TL=~k4x{Js$grFZ8_eN=wOCXg4h>S$_Y^d!l zTTgrMPEf(nMBkxjnUSup?AyDjEB9k!yX!p}gL)I<7D`_JD{i)jtCMEAY8}QRm;$FZ ziyel<^&2gAnI@G87vu29;s#%sdVKo|JQk%-YQ#%cSir)*sv8Y8$za3F8Y{JqPD40t zrY&D}!QMy5AK5D?Cp5j!eHU}*zyu4i35p&c?*Z4EYv*o@3>u{$v`CDaeSfA+u`=2} zhUeb+@8U1M5MKNp*JRP~u~z)g;9i7;QI%0)wmD(YL5&enJ#5#t6dk63^VcxTx3YIJ z_xre#`G1cq*%U#1yk0dOKh5u4ku4UF{SDt1PVcUt2pvT9Cm57bNz~0P&#D^(;((bmW|@4&!>4DwEj(%TD=YFIl?=V>(lwsSVZh-4U46=1{>4Y z0-({$%q+On`oE}TkSHP2BQV2|E`=4h0;EY;N`U0NqyEi|3J4}P{VT*v=Ak!$K!uNg zm*}WMDch>dq+W05z+!RDaVCkDypN=(ubVDYMEPVE?8z2oo!I2aBS2xWlw<FV6s;uMn&_;7XC8QQmsRE1!?ASx!{uLB(sJbN^F}#|J3S^SJqvaKh-cSS z3Iq3Xx1HV8SAva?ziQa^hg;p?>e_pgiyMO6F8v^gXFY0eURk_jk!$kY-{_O&^MyW9 zqL}-BSE3o+p7lDL8@#bIv5d=4>2E8i5AS(dIA$hsUIDpo=w(c%Wj8q4$tTRvb3-aY zojoSDYRf3QH3r90P;DBCwVG~Wy4H>G0;e+cer_+q>CB7_gUmsY3=PcJk=DUTtdc}s z#loLQDqc1*IBUXc^9{R=?kMw}$k#WTf?)0s9ukiPsir<_;@4s}msZ_Sbe(jS=%F7k zybDKg*g#I3{@+gfoTM7(HdfAyG6wRQ?vL$NUKHfZvmJNK^}Q;~RI;c7 zDlA@nE-7joAzXp0bz&RK?~V5D6Xx5{op!Ki#v9?o#GP+iwM}(Inh6y6-LvBXqAUS_+^BFU>|5W1C z!DJLu5Exj;rH*BR<(xvEP7TM?9*6DxR|Qf2e$s7<096X9vcDmq)A%<8K2ck|>%-#I zEGAz@pCcFjEU0q#*9L_9HSe^m{zoL{YR=GC0k&|mB2j(v5d<#z6AhI@A`-+&JDM(seE>1L=x!m2jOE&#j95vx(<5{>{cL z>xX%oin+7hDJif+Px7r-(DkA$$GeeqFbID#F@SIYyRAKkH2JZokC-o+9E~NSr)g{S z#-$SHA~olh>@HU1lj@>?s-IVu1?5l8eMzXD6$Q}QLT+vps;SW$?)7y<1dW?kD;T4A zFCms&`%y~w=tiLby@Tc7Ebs(1)p93yZ@vQoh4kbRVTTT`H`OB*ys)cLogT>59D0np$ow z*Bogs%o~urTbx`r^Fbp3f+G=t1zO4wqeg6@LIpJVVio>t7c34{U$n2QznXJfP7sywdb0O1F1gqaE{RSbi5tacBT>i7@wj~8s<$kR=ISX^5EE8Bc zWj|1W(5L!dW6WVMH@RV5W#w%AedNC8^jjUX#vFeQ8;y^}3rFQkU1N#A4C#4MwXXP3 zWuhzS-@-s12PmT&_EaTBW2u8exP=JU_nbFAf3=)6vR5YY6*aKK?@Q{?4Qk$55cz>Wj0ZE*Qn_$Sr~(OzNo* zLrL9Dy}F%~kIfovbum$Eo*ngYw*1*ilz8O-T3g#Og8po3R_>8#hFr{OELm)+(qczmSrDP5A z&>QIJ#+X^|%Xs!jRAB5|FE|YAC&e|B&p(eOj+MVAiAu(}8za}&c~xvnh&kdwhWmfM zttD2y^(95S2HcY)T)FlJD%|+UGCyb#fo>qmxKrwJv8lt7U+eJAd-+VEy#z)ZYls9j zJP^?Hy1Iy6yUq+D<3%b{^3d|xNx`tUcrrc~1?vARpuoY2AP97>GmTvrN05LPo_m?1 zBOrqDP%#4o>qPywS3Yn2@!^EwK^ggekRc?euZV;dLWC4@HX8c@4mnUDAWUCvg)OYU zYwII+7?-Vg^rOKfl2f_Tz)?gAyA?Ep`9L82jnAEwm#&_oyswg8!v*$F!+I|tAMIZS zDeOy2*k&EsX?nKz=PwdRKebt9?CcXn!^06lF&k+Xxi)4pG;qj9JPq+RT8AbojXG*cRzl@e+#os<*HUu z(75)`tej6(KOFt&Cd*e^>|?P6a~Lxj?Gnm+oa9g5mGL!2far_Erx(^NJp*D}Q}6Lx zoV8yfgOFMWB1;AN70SBpH=i^Tt@w8L%_WQRptTLyE=pcfv*Yz|L$ieQ@XVb3N?S_y z)u|vT(028FWU1!qrJyRp&0~HRLWFOFY0{woH;Mh>z}douvR0wc^A=wi;{Yf6ON{O^ z?LMSv`7xD#h#(*&e5m?h!aISYedci1wFG(ofF%jVDc9yYDj6JXvtw-5kp-@Q*oA`^ zic_hUR2JIrU3}K*p`xf{gAIDc{#Zz4j*zH3hA%8UEif3sUDg{^NBkN9V$n zf@gu^6@rEzQu)8LkgJt6vH2FL}`I=4wQpt!GmR3k;(C0%@WLei{p{FBRhnNz+gM6Q1>i}VN?PD&mU zox`Bclu9eS@X045<%WPhcB7db4XTe}1tY+OTsc6r1Xlf{B?s0VDbBwRFC-M%BaZV% z#kZ-bt1tbH2X1vk3ThxNh}k!zGw+e|vsQT&N(uT$CL5sS9p#E#u?>jBj(hFw0k#eVsn zSZeJx?(5)VK^2%~kPH$9poWtglhu62g$P%;g~J&8SA_PUf;RnrR8+0woC}dFx9cTa zF{{~usAlT4zCap=lG61df6MVS!P^@+pa>qmh&*;l<7zX?!8d|YVQR&*Yz6nX3bOF4 z_?j#-hl1;LQ1;iH3Jdf8-x3WDjt(J>4xSl~$FY_OK)R9f`*b4%k<-S{Q_4LPqU1cw zY~?CU&3RvY9+CDDTl5%}ro4Gp@2kyH5BL0|zOMm-YRz@BC9I{N+>_ea3mNejd91kk znHO4#U4`L9pLTsp7RMmZt;*LUV&Vjkl<=#V7V1Dra_=V_{x~NYSEciUNkA1wemcRO;Y zI06Rekw@jFzvOV$C)k9H%;Rsf&4L}=W?WgGOW#U;T%%vwkcbNZgePat-(4T+-sJ@$ zu+7^p#z_U^Jn5#CFLiwi#apHB;v}Q3*ArHa9trMB72m%l@w6%P>adS~^YORWa~2u0 z)85*WXhtD?gf)CBFE6waL<+z9W8yG0r_%hj@p!`HVT{a*`wN9sUdh(RVx zyx;QZfDm8|UG+*{S0SkJ$os0@Xua9gY|+CY0kl<>I1q=_&UY~s5@ z^;QDdn8w0i@tG`VWE%V7f|=j~dfqDQHeHT-oY|&FA=b!RwM3Z0dNHZdT+N%soIOAm zCpQduaRx##3-ZJpi9%3gISZVV#Uo*0pa&|25`PL6HN>q_x2H+*E4AB_Jly;gD*m?r zhW)ZaO$KS-&KhvF{d3e_so6F@;$yr%NU@!a%Pfa zs(JAbVB46*-zN(-pA+mZw>objO7mFIXw%pugJbl;ecJlmi9YA1AHX4@5+a| zIK9Vwd+@5$-Y`>wH@@yZJc%1%yf?fg;@b0gju+dOXTTI+sJ-kr+?17Ma%nr5cA_Vv zdtyaRdM+bG!iC=!0iqu;hdyp%@TM#f{0Cfm+|UC+^Ul6(K#cheJK7QGT28JH{`Q9_ zr(deTHu6<99x!2&85P6^grJ-;>-GD2II=sC^FWsfDP1QN*Pj73&V?!kWjO?%=~m`D zKUyz0MIBCx0XP`-5m28su*?i+X}kneZ!zQ{5%BO>)?Lvx z+pJgEgbsw5qsM^j@bSQgNEUS$9uuN51>RGlWy{xL)zngM2}+Ved!vI4wLr60WLnzYSH4@UOfP#dyq)7MB zBP}g02&hOZ9YaVeA<`g?bay`Iy8qYxd+wK<7kp-)wbwdpe`D=;R%}QW$@)A5Wu2MO zy<5#F{Ck+aWOP)*=8dq*eQzW*Jx~RC=u-@Spw@@2^!yH*wpL3O z(1z1}fTDaKsF$J%HmxO2ghc0Du`~Itw6eXKOfoxkwryXk-Qof7@OOkicZoEN3jWyX zYZAa8bCvN_F-M`tYJ6_f#M>D~ads5(XXy2jzc~LCV~De)f>(9k-D9e{i{m+5eUF(J zaHwlc2z{(g1niLBPkfcH}>{grMUV^uMfOA&AaU( zT-aZ8bC6iZQstcv7%Mdv#m-NQ@+=fkm_@W%nuUwMeet|87ku5|LSiiH0*Y;UPWIZS z6*uVOQh0CXmk|y-4+^As#b{i8KZ&mVI695rTwW3n4a_6~XY)NVP|D_Xl2t_vQK?`5;p9lFhxo9Fx+{UjHJx*LsqG#+2 zI|^EF@2t}$5g%N}NN}ebJV{m&>f@;V`*>RooJdeciNUELFlBWo$~s)T1LBL#S1!lh zn}1-eJhJw3MLn$i4+~!NKmdcBNw!Ol3Nv}QAIla|_B+rIqcpX!^3?XN@1Hf7WP)}( zx)-)c%z`DaUN#0;EHkSPrwNPv!I}Cx3%A#4n1s{+TSPZGK~M~QCv*s)C$!TIlVm^E@Nu-n`{Kd(9$;VGRBK4q!F`K1~EISmV4WLmRTuzE-;KZUV{>mG(14WQ=h&JoZ$xlw(BC%G$^ zh^S@UMjPTTZmW<@H-c66!G{vAYu^OQc0WKdu8TdH?^`Ezw2>{#I6IH9Oo6lg(^HzR=y>g}U~a8&NGkuX5e2wi@ILvQ36t2FfWM)3Yz zn0^gY%nRpHLEvCFC>Ac3%0mR#L;P^;ev|vocATn~ATbpLt8#vfS%K$P>36;q_XNgH zd4@Xd%Cny6j0u%KLbfLbg_RlJOg9)y+!3&TA;Ck2kN5w^?3g<}FfY&uOuB7&E(k;C zps|&mCn)o_`VB5yL{C4f)pd3xx0s~n%t~9!%cmiHJZz)olWKZtXl&oQxQQlHyPOYE zaMUF$>SoTn*-Zyb-!Y&Z%_#E8H4c!pbj&KdL@u7T&(5z#lSk%?nUReY%R%nMXHpn% zzak}_^5U_pw&9%dpRFER-Kt(F{E4#~u%{z&>Qat-wPsg$vzKkY$r-)S8|fT|dy5D7 zSL)1Mj-th%YzZwh%m-z5Ete@DWb(J9CU)-(xzZ8AioJ8Eiwovn1ZB@@N_VQNIh8 z`?6_{@|AK|6Cu^bcXSI61`dO0#;YOrf~s#nMTPw>KNW7hmusEDLK=w~`O zF1A7T&_iaJH&;mC`I`IL>$=V(+x3)hmy;8*j#2c)TXRfbTDdYvWQ1`yyQzb;21(;0 zC8_dZNTr6b{D(-;60~TJu=^Aywu2X3)OZxawJQOc!rn8I@w@9EE^9${IzCrN=GfG7 zzXHrj#z=5SR)(Yy47U4i!CJQ;SDn{bLEj$fWs3jzYScqA)Ph~NLl9A zr{^5^uQwR;-@jWS1r9dDnM%X|wv*h^3d)?&&W8HjDoU49y8)DL66F5GUwSYfQfaa# zKc^2qohS^W%K$z~?=|0LDtH%BcP_g|D+Uw)es+O?&&~6-m!6TK)8B$Q zE10_E&uJY!J57hqGj$fmrguW7-+4x8P)t(9UA3tUy(gca?HyMb+!ui6aaovPe5x2tS2k#!sS2kn%mP*=qWCRKv25@&hFqI=?E*oI8f8O< zWcgsl4c*zGC|1GNW&T412G0LVfY+At6lJc#r0MCS0y=zzq>=!>h?T8QVg}S0yduLt z6lhERAKE>;)$QtG9dE6P-8kFj-^x;GB0%~T<}*>0&0h>Sl^!38 z3S3`kqyg-Tc`}&WZ;!$hc+$r0XN;)Wdvh{wlkX7E)mn>hu7y1%D=9 zeCr+^>E$3OIAAcM_QZ}NcD03!J?(2`b&Ug6BHn&EOHsfcpJU5&Ajpe&b(gcNu2yNg zZAGzF_kQ}FLM4`my@qw?F zoWu&-Dl-rlbUCQM5Jbylp~%L6tKYgD7Qv>BzWs9Ok;}GfP4YEQD;tCD+Yv`{;&pLN z5?xZ%i=Ea!ygkI+7c0@sfCyps`Yu@&;9IV=nJUDs8Ydi1x{SkwESV*60 zUoUwn)0~~2vsWr+J`_c{iMF^~gU3!qhjE?ixq2(&=n}T*7K!+X?Ex2E#w1?Ya*8ey zG4eM^RcnTM*#y3u)jq&mjHL;93#p2H!T~jsYG)MKJGwixtz+BKmT7pCt~y#2$J>|# zI{o7haXdfo7I=Ng+FmmXhrLupG#!JFSgz&!B%-YieuVTs`rH1{^!@p2g>mf(rOTz# znNy4O(fM@_d^Z%uO^^B))i!5F<*_9}5Hl^pP!l!qelotKhC(eVy5SVJ#-bn!pB*a2 z(GBT)uK$f+>4_S6N9|h1dp<|LBY}Ak9pn`qFBKDbY}TR5+C(}cCW8|<cYgdDuL;h{fb-kVpGab` zrzk87&1~TcnYB9Dz${T>J}5pzhRD}*lcm0(?;$qiJz$srqZ~oJtxL$y*-ryBmo>@g zr3_yCDA~XCkjZQBr3Z$v1uWZnEMxtjS46$$)mi9=h@MGEQA?=xJ*L#Gt-ZnJIyG&E zJxy>7DlaR}e9AxZGVVB2lkev9eGL3Xm-thw@v}KH^go~!m-^ffmDvVW1hwxjgOS)S zi4mX8UrQa;rE8mtecCTvEwJ3|dwO=Q$ z?Z*LmUh!J@8rjTw)xgF~9qXhuQTXTo);$5Zt|Y8Y7Ef`d>>f{>7!}$Uw#UxC@{`;{ zpjTD&O+S-cu~NkdRbj3yCSfif{u7oU1O+OII^IYG<{?b6N9v!=-o|k0()iOWLL*L= zn0%JbeCqQ!_AZG|$iAx3+u%ezmku*iU)-CE;Z%XGtEBk~vb46>@mirLs=Gwlc`OF^ zulQxI`d$kxWu zYtYCFsd5=FCCLj;P@22~f}|t^l4V7C?H5M{JjXu~HyhaNi=oIGZ&E()^sUyfLLOclfh+F!1car8JbT&y#Js1oG4mOyiI>v>^S_i&(Xz&0LJYgfLJy0!$h@ut z<=3XX={5cL1?x6OIVw8Zy4xzA*9Nch$Oq?l6l3FqUK2Cb55GLyoz_vxEw)iQoFxxs zB;SHKS+K}>pMDW=^aKFI=Z&sz`L0r-Un>#(yAG0}>HlPiVhRSU7RIg`qb5wPi30KL zW}=2HjO}6QNe^ImEr&40?Q4RKVGA3T@$J<_cdrU0H%0ynoG3qD#3+7 z;wGZzGw)Sh!>U@B3RpfOK}k=bM8C~5lOV;hO`?STO96hWw_=F|eSxV=85c&a(FM~& z_VBd)i{AdpK2>D9>MPEWvqM$HnoM6J#G~SD!0+QO;$4=->sD`_;Fk%I_n%+bD0wzp z$G@H*7QWMAo_Q3+0b=Y>4A2f$Udg&qKnz`IBoO~QxzU3F_ZHbMws#KOud^e2TvII& z%fJX@8!#yeO%^dP7W!Q&i@Syl;qJe&$$)UpdNdkDdT@rIz)|!Bi}oa>0YQv8u%l$?jyu6a zPSD}{O>feCvekJTOFajLAk2RkFT-4S8_0FA0r|9D;?fTcI0uukQ0Pqbgj0!6O(`c? zCjHwWs;4$D9fRGedkdKeolQlVXF%#l7)XJlolHnKPYAkdc}T$6t#cGD4Ao|2ON3y47QiwatYPuT z{Z$lkEU;Xfj&f#MD~ZQ%G!q1ew!i4skI)NPJ%Z)OZ9k|U0FJw|u)GyMfn`keLPDB9 z6Q+*-h~c%Y$#1=v7w_IeURxX#j4Zs~S{Jn?o^kIEf@kMA9kEMXhRhDjl0Vz7kY=i5 z_xTI{5$!7fZ!(QR!B=k~9z)xa#(wNjR7kK(NY8Div5LTQ-x`?UBq`o2IrDCkMk)*P zwE$Hj+@-Wfm=*=>+DcB+uoVjXr)X4yI$n;4c@jpq$Dm?HM({fY8+5GHl$fE} zo8I(!pN2=UaGX47V4{f3@pmD4`i~>=T=ZOgCJPjd^JO6qPbc_Kqa)GwJFyU#@zSvP zn^R&^`pbJwi6z*go*Dh8FvT4%H%?G%`C+#zC#ZTjpa-6w%yg;72JK{LIp=(%Nx@DA z{3Lv{atQFXV!Xcr0T=1qX%du~P9_-U&eykw6r8>8!O_MPQQ8BRITDw%KY+q|v@Hc(L&N#C;qw-+Rt zg<3XlGi0oU;a*<83Pb585|(DE!S@<;qrj!JT6L&x18?mvV5kE^w<%UF3{}C2YzKK? zP@J~CevQAjOm>5lr#Jn@Vpp4SW$$_QDx=-6!pg97v7yZib_?4F8l^dM1lm&D_UY$e zT&wk@*E|OQgI413t0H~@d`R`st^^LL_Db1-?EcWb&uK8dMNdmN6? zy91tsrz>(q?RQCy$_IEq4r#urCoh#;xs>v8WEcAp_Xb57A82*s$;`xhOLq+muQHWn zNAuez2qyKvf-MX|_n$eK=H4Y@%j-vR|K@4)`?Yc%`}1q}d|Tfo6@RNtbBpV_n}pkP zRF=Hdhb;31VM?L8-@JSL3y%pnqqBctkH2d$2xXZFL(fc!O!%cU(65Rd@PlXQ2pNR%0x&^9oW)NJ>rSs)D<-P5Pgh{ZYZ&5E4#doX%JhnCiFuD6ts$;j= zI=0xHs2Tg=+lL8|CFgLwNUiA7FV9*?4N3=w^jTlk{fm&4q?ZAC#}vSOWP_Hw3=pL- ztveoc{aLWt5gugYaQgiAM3iyFCN2B0K+BJXM?JNxh9nI z|I?mS46x5fqIx(*sQrho#-fZrF--uj~ z-?h~L-Az+O*f_Xteyz6~UnKYLuSo|$f^t{d@Hm#P`RIUg*SYv*Xv(PNB6-xqbXY@k zx?{uXm1?j>-KGRO$GgB?=d6#QzB&9 zC*B1N@-YuDNfptZ8^V{aQ6!_Kwrh;HeOo}s`;pgjww`ZtLaWP^&o-Khb|62{zFn~? ztBCWiIvW4;#--bB+ZFfAS0ulx%|*_mPucss@mI?oT2CGzs5d^^bI$0L7JV%yeL})% z*c6}AQ!A_|^(FaBQw)Lez}`T2F0DeJKgRR&)e9x_v&xQ z9~o}5Bhb}qiJpKDNB1Yet>1qM=seusYQYuHMyDW%IqcZH;5|DmS&j^_OqY55+ab^*3Ke|rp z;o*_mPv2t-2_cI>>1Mx!^IEFunjB#jB$gW};93TDUzv}5Y;dwn)Kt?+0~+Q?mqfv! zM}``(wlT3?l(6#}nXKyIoeOG!)ANuI3?lmxa3Usgeb8Gme@WQEY#B%-{Y8hDaryuB zISH!RY?Q8!9Wfxm`m@w%{hzRL_+ULYLo{UUe)tt%x|P=|e`X%LuDEQdpylBrv>x-$ z8-0xwPY_@(Pn!~yfsCf~*9zFQ&IeAp$R-jo%m}vrx@2fOI9rnf$NH>9& zC=@`weIe!7xri^Qp1X4%N}h`fUQ-3tTigEVK<)_5WPm60jE)DkT)R1amEn zrea@H7Iy}vH-8r@ApM}%>3**j_fw$Dbhhdx? z(4IqRF(93vTt507mL z4Y-%FWD2~R{My>wlV35WUl@)2p6~%)TA!JotkQtWcb#uV?lz5@ylT5F$c%LUfOE!1 z-f*y!0kMh~2@&Ojj$t*f`GTW!&tbb{>0wPlO2}I|QfY5)&B;qxT|tnocS=?LlGFpl z5A?M5mF&!oyy?f-`7pg^i)-8mu!Z+=INxI^gZ~f9{*|0Svse3?FF3#4*3Zz(~L3w@n!|Di6$BqsZ-1@YOoLwlef`N z_vb+sJMb{4G|+8gG5e)@onb2y)XRt}Sp_Ibe<@mRnN>lxC6kRu#%$v)f1e1{cm)!5 zzORqY8(qyr^&`}{P^B8kkeKbF%Z?J;(@-MSkLq9?AD1^|c_Wg|3;bM;5E5}alIVZR%+ARa)*uu7 zuAL(J$PA}Fc?tGyTKkrkigMH1H`02Ck@ouD#wpCFb*x6Eyr-5kwv3oF_Xr*02S>%i z^P=xL!bE^vc;k0R#A%rLSrx|>V@+sw;KV&pkydxMdF0O*F=CT7!j5wU<~Qu(*rx?@ z>S9kFLB?C7Br0(e6g6Xcx6j$3a?M}(nO^%fIO8tHppWMX0Mstn`2QUlC11%(SnZ{_ z9=By;+x4y&qNdY;%w;8Ll|5CMXub}z7{cwCA9m-fWnuE3&0Uzf#;8h7#KgfF|J}t7 z93T-GS|lH{sJgvQN~6k^05PDvW57$9CI}2W2%%?$0>Q?&Mx&x$xp6qX0T?znKsdU3 zQsLW0-0q14ow&2fhQKzT0DUj^2jLCG)`b4EVdIZrfGt|=`uqgRwD^DKX#{c z@AMD|><`G(xy^B%sPR8k@OUJGgjtL*Hz@q|Jb7;OJ@OX)F*hpn!G^Z2xrQV`TL(!! zSxvG9(xhfYGYo|TxCRzlb6p6gIseMYz3!zS+I`UCpHJlIHc{{D(SNyc%QDac5#8{; zClhpuwg`Q2$w|iXpTdom!r*8l5;#TH@7FOP+rI`HsyYq(^rT#@i!b_b5cdC93vlwa z#qhgSMAz-{ZNDvCvoYqJc*w_SOZs-bEc2>2g{|x6qnu*kv(^c&{c)cthIrt_y(v$x z+0lOz&2y?~xbw5WJPOr7+QNCU%lNCc@}$F8(9?|kP{1cJsBZ;NVBGDrk%A@tO8U}G zZvWtB_?s*Nh5nkpU&y5@shCgDM?CNIgTRm1x#TSGOAU)eFE>B*U#e7HlCr$xIeg(3 zFgN4j(S;4`=Z^;9L+^B&oT`dXL9wk@ztkLW+s2EQ%7~GQf7~_zQD=ut^%j# zN|i^)jFqGjp(icdhk8rg5ydrun1EM0VNz|A07hak(gRcHKKZzaY=_a!?Go1=WqhAZ zi~%vkip>yc*DsE{{%LV)i;$+oy?!~FHdHrCaQ=u%Bq|Nql3|Irfm%z;OL`X(9MnbZdu{D6}{k`X(rUr(hWiSf9b8o|h7-=}6MbA%-C9cum*XkU<-gH%54;U*p z9*;-v?*4*GsYTwWrYQ}}aA~5*y?0{i7&Ni&+KXnq>!hz1HptmZ9vw78!EMz&qQ zkGyF1Q3?HOZ&_edF-{}i9>tznu+0qqFH}3Ls-T0Q{Cn@Mk%v0?yWy)gRuyWQ+nYw$ z;R(Wm2&>yN8BBr%{36kc&AeHCK!5%~<)H0E!&nw)Pyr5wU~cTwMuaH6i$EtU+T)Gx z<8w_ioXQQPal(>+hOYf|vp!Px9>rJRIotiCPPOnGxwk8|#^5bm&wta*!_P0#-V-*Q z#HmzXxK$QPNGpf0t)&(`OSrEYGO>|-^NLfcRuRvlYEQhe>vlF^NFhCW#xf{co>Y}m z&Ouuff~SJmV{NVZn|C(#`I?_l1(D_P_xW(E**dH3og=#B!TSl*3o9t+a#Y-{^k2t= z_-1x`HmdyA1PuIW{7%y5C3&AMw}7a9^CnJK6(v}j>cWg*cQ6OqqRpze=bpKtrCLM@DsLXC{^k#qjo}OfWaxBBO=T?L|CcsGAfK;w^9gq4F<1-F-%79 z;TS%DtAzMrv3cvl=d7#uyN-oP=!##!$|WT2$so!IcUR7p@Ths8_w0zLhLkOc)H2Wq z)E1;?m07T`OTkRBh3b|pC<|L+b;0Gz@&#wPp2h!)y~8U?u>UVAY(xkJ_^+_~H!6j{ zkRcz5-sc%9BA6n|$_j4og8Z$(9f zV=F!{Sy~eQjGi_Fpl)<*P(XApA=~l~kf4PRr3}wfIHB*^Y0n$3$nqR=c@|ca#6uqh zwL$|0&wkFZ5UuqTLr}#OS>)38R(Fvg|1(M^ul6JXs_J4Hef>vjiipR7I^fm*d+EpR zR03OOnb>*U%MX_{rGe`<5_^li<|`#^%g z1oPS^FiL-a+^2v&QQCU|K-ZnCn(ASSAk2__uO^+lXj9_V&wimQg{zNh;OAUA!q5pf zg1bqI_Yd1+PV)vkD2mgHI5WV&OtDXLZYoMlF(pCV7*J^Y+VK9|6m1|WDKd7 z#wV)D9jvC6VQ1lTKT&kaybma+m(G!o7J`@MZfpC-(3lu8D#c7exV=W}59zKrjMRN9 z#Zmbu$L-Bg9IleDN!I(P*S$_c(=+lvhm-HV&43Jy0(T!ka9k)FDF^($pD4i{6L+3h z`0kK;yZ%<*TRY5cEB(%6A?o4Iuw1c%kA&eCX>d_+lZrV}*v2qG?zkgzRPx}^f8{m- zA8coa7=7b%_^E$8mD>GkCMcQ{!5L_Bpk+Q);mtoR4Oj-WvUD?8miAaEeujMvRAchv|OaVz~GF=fnk41HtOXS zQZZzErFcw?<0}w4i73Wgg0H^&IyqEOn>yXq$_npf$@kr2f1Fr5=Xj+`lC9Z0Iodn!_n5wtgsm z+5pj>tGBb72|l!#=v=<=%~a@S7Sj?7oS~Fr`6|s+S#G^?lV!L-F|LKK54oG|RND7C zr-iW0PkYAp1>>5>bz)u#K@qV5eXcN`&x*S7AL@*6RPt`6Kr6s!qii!Xb;P;vy<4iXc2;1fp9s<(+S@cFr^?# zPYvGM?)8Cj@6ePN2T{DKeo(=mXJq)|bZ#q%V@7)YiG(Pdpr<+}H)jN!%bD%^BEzlSITJOfJy0a_J=QNH!vO^Hul$w2@vt7l_6=RC*K+ zbsUBq{EwWu^cd2K45Cv3q6Z1|Em+6u`(gV1^!Izi0rOI@&$F?i zJ$3LR;~hU~0J2l&o}u{v!->U3$MQ+aw}t@%B_mB+0BL<$u*K8xQZnSze@Oemokt$_ z`Jz_vJ)tZx_8p;YcAE`RXayQ!W7xv)l2`HrP+gyS-AZiEbA^`mT{B0{dd`NU%(+BL0vGR+9b5|`LrOHhiGzS= z{Y*Df_OPq>eqKgj{UsfLRasxS6wM`LD5nS2zyP#28c1}YPWHmjdJ^(|gy~J44TqK= zX9fQ%ATS8jnHf?1!-(Dug#gwjO)&<>&GOSw}GJ;)%+AjrYE?iONPU!KawEGc_Pg?h)@XFuyf^=OCcj@mwS z>+9GFEyJ9=Nzk#Syv~`2cp?O3D?6ZirEIuC{xMXB=k^ zr+gv5xD}d}rAz>l4rlu%es<4V*98GZ&3#`XlY*gp9|^%q05xrQ58f-hE^i-Y<|sy< zzHT|8>vgvX^mw6Sxt-y+tTq%vzzuy(_Y{7^0EAB%njVyek~kdD8=l+Z0(7`6Qm}>M zyZgF?w+uu4u;K*BA`w}srtk6+*6L%3jdw3Sk?%pUUl|~GiM{ikim~&=(hAV?@A)l8 zMAOl80@4e5qV%R6NoU5G&10*x5-Y%{cgZQ*UyKZCYC_~<=Fi4NC&`f^bE%|L{|By8 zQbtkgzJ+iU3GE<3fM&s2iYgKCUncD|$f`=zS&3R%m+^VP8w9Bo)2%+Kfs>8s+S5ck z-$e~{9Us=)-fCU$!>M$N8ly&OGf}eQ)^}qS z^{%iGU^eo2-iss^|iuZwf~<@UUS`sp0@Y*M}U7dFCo1DmGdK;gTnM z%7`WlI6msu6F8!c!+dUB{#-mF>}2FQ@j1&+mZ0nxt_r@E@CiO9JeT7lo$vsLf)N2k zA}4g9&hqhZmMjVE6%+%W1GK#)C=hz74+GLhMs?yQEi9wATGcqC+^&30%E&oM>qtNQ zx(D5&$5)q8u!Zw534yy(y4)@rwDU{2N$@AbAxwE21^*bFpo16pk{~!0XK{Y^ybOi^ zv!x&qx__KAjx1{xdU+*(7%>W&Ne1~&9IE$L(l!}PezHKGnG-jruv0c%q?CN0|1`2O z9yCcS+jX=l1oJ2Kn&JAvdOzx(F$*JQ11ac7P<@sba1JDT!MMS&c--|;r8$05+;8#o zT=EzGvG~4xz5B7}na8Z}3cuWnd;zy&28HhtJ^Wsu4wzZ>n}Nz*aG`+EWxW5t)?;%O zI0y8s6oA!W=74BXIHICA2=Gm=z4uORW&3Olc)S3w4*P=U@A!Lm-M#|js!1I^yjzox zEA*9N3)}9J$^lYuJ}V_F-_+JW^K^?bN#eMCXUuy=Uq2zs*@3%w`VVb zQn;X~Pyj%Ti~L`c6;23NrFn3W-vWYEk}EwHKK)$_+V3|~RD?qjYJ00~EixlYnlt2D zIf!JA+Ai^Z#JQ|2XdPwgy zKob4lr~EG=0S8#&3O49N7~}_afKFHfdj7X0BZE(H0{kX~N?HisF;8{>K(ZNvQ_)-mKQKm}3lE`XOV5aKCP&uxLB5-uvP$<3WD-&yw-St4PD{*X57W zlBD(#%pTNs|7NL)R7OOd7k}n3DAO%(Pcykc;mN3DEex$V`R2@D%8aBFxLzw>^+k@r zo1F%Fh8E3d<5pjVoUq5X;(tLMz^Ws^1?|f{u{8OmE%o_>pXB)4i8HIu_?zZSsl4;@ z!t(u(=#Nw2(8tG=rd@Yf+$2n{50mV5R{oDY`A-+DfX343+%T)O2xzCHQ*zZj{#42O z*S{1ky|9TTcwL)Ud_ZO-^sG=yEvs`WDF~Hlt!tA_uUJa>P1VB9XecU$0;$xZiVp)` zUy&-UT#j8^U{U0!#3IALjoCDb{;s^h&0boJ;t|K0qT#2uKYIbNLMTASC6Ib?_gA_2 zG0pq5kx@mfqfJ~+QphA^r46`5<-b?5?#-g$eM)q4KQ@crkoJC=b|m=8AdUt`$)*OA zEu)|4j1a6l5ct9I8)v(cUK-;N(;h*U_*?7zepydAC8-?sbvG5f0@XXwoPXN|PTmxne# zgL#>++;2j-c@+H(&W#7mmDCW>e+L+ZRFtCS$V06Dr_|$MAz{Kh&3JOp>afz7p(1OI z3Ac_|i~Ten5a5MW$Z{`w0Kz0}%G*&!ENYO@J`Y`0dd#8*UaF=gA+Egq4ZjmKKXUN^ z4s}P8tK_WhJTgvK$>}lJ`uf>FM)V906`L`~OF#U6r^5UE4HKGjoj)6+@bP0)jew8D zbMH0iZG5u}di-1sAQ5K6YF% zjO8A3?B17e+w}(fc38s#(7Qa)FQpWj9^7YOpD-{3oS(R4c}BXt+16(8$1{hMo0i?M ztr4@Jw=U&R9e%zOt+u^BeJ8r7s(OX1IpNh>Gl6K)|4RJ@{~5_wT07pMXXeFUgU?_> zbvHz3t9`j0b2Asp&BBM(c-)L{zn**23xZJ3!^n)qHX9&L1fc!}Q=55QTK^|vw|4nm z74eHXzzY(Tn|SRk5E{ZBbvS>qw?)(M*n@A_=P4sw5*Rv3k?Z$!gNBs?$vyh!#S%ug zc#5nxnzc#Xp;%w;o2rkwQjIKPlX83xwc7TlK&8DoUWS0b`D^ow6JI!}+f!RC#6C)? z{IPs5^B_);b}?i_^ha4LqbKUl4o~;{-8XVIfnYQk)?P9WU#|x#&aP-W<=H(Y>!{P> zvDv7c)!pY-v@E(ySBa{MI&TEO=EilUX>)Jaxlw!mWv(T*|@j5vS zJ6ZXZwc*6cBFU(WBr1iGIUX+jMXmUF!Fl8vC7a|?rIw6b4BvwomZebHzdLIcdElf- z+4umB52t2&zt$~{V+9B`gRUf1&|i?in#VbvR9{wTZ1bkXIcM8!Co{UQoQ zXY#U94=I>Dj4a((c6}5d?d+7jHV2ghbJ&kboXcGYEw4(Jv_5ofMe>uD6{WMTOyez; zihM7r`1`nV!E^t3;r!8vZ}LUawU8H!KFJULE&eLX6Q+7;nX}HMz4qu<@=wXR7=>Z% zo%Pm7SwU205|gPP>YiyAH`LSj|K_iz|Ni184E}8$2rTD6fz_X@!raIAEz_99&?+bp zP&4x+!>29ZlRZT|?eu@{P+2~j-oU@aA3GTT=KdkkJHIrk@XW@8{yI51-bkKZ zx;E5ty(}!2v60^iLGLeJ#zX3KS% zKSrDu<3l&LP_IJ`*r~iW1;TF~WF*S&9CaCg=1RXgdmR%}>$)6A_$1ooCc_2%yCvP@ z)3sYr{wZTp@~OWB8dCnp!_fQZXEXd(|HZrB3*#$({8sENQ3~IxEIZWLN!9XVlvCyE z`80J)paG7OqI6?+57$0&T0krkeyxmquXsFX?cDV()A?vd0*AAtGksd&tCg_*`T3V+T&iaNx3oDOu}nqvb;V9kVSxl@Kw=C|`Xjio8Y^ zcjm$%n}A~CIuA`aYA!AdpKx8dWAyl4=h@%B4-I4ktuOEPmC-I~@K(^(YRvAxe_G<~ z8vCu-9$J+;9rWYR#GzaA=A`I0@8}=><2REZr%bPvRx~~o#62pFP%LB3yLd3BI^tYw z*bM8uy?xoC;=O~fXC!g03eX`xl|mq~ZdfqLxO8dVI(=*^|D`6^PV$?zsXDm~Mx}wR z9|C@LM8D4CrU^EqiWLTr?;bnhf*rqG#qp|`o=Q%8O^|sr{AcB3`a|CbL~r^&HaBks z*DswQi3<`$$yt!5`p;Ms6XDK|l*T`K`DM&#jh7{LIlmc%yvWofs-l-Z8*2wDvYm%x z-80HIQAV3`TGYJOj!}hXtiayBK>1hT&)67MX0nZs?AR<-s1N%E(3H-ak_9N^#9y6` zXcH)X3KF4Y`^^6|_EWyJ%lMRWLaEa*D3QbDrgA6^dTm%02&6!dWXZ5f)ss0aFlH?lUd zI6-FTi<-$CufuD2_Rz)ozghs%t|~SavPz=ql-?}$0Y;77ckmbmeI3FZPoZykcb*bm z#xRhzHeRZE--x#+dfDBsen^|Ioi=+(_jo8`JvHDNC3@Lr#kG)-fU$-a1u3UVpS{wm z)rtSs9U|BiFr~F|*ewFDwpSF1XzMl<*fDDTDb53 z-5?s1V}7|Xwo+TXvGQ`~6mQe;8qT`u_q-MB8{BI+D9D%297CO@BYZJbXVrTCCSuD6 zD(yTkyjW)VgZxo9kLYe#U+ZiObKrGm+Wfg$E5tZA2wHP9c#A#Rrtf1+K=^)VUzOCu zvl5?qB^I7c-{(&0-^WfWZSHA^0Aif%RK(P}355844iMsQ(HEKMggqtqQr(bM6B&ou z^^m|xL>1$X#O@n%cR@eKQN8C^Y-|wr1QBT;$n;{)I(3DaxckBmGqbPK5)RQ6FUYw2 znTcff+T!FndDAR~zxP`k;)|!hOEL95?^vB#+cO}^arlPuTM^O&MF~Qe zlhV2GJU>q|8K$uXE57OfppOhYK>-TRyf~Ba0s3+l3YL2;96e&n!u<|+j{0d6XI;B} z4Bhjhd4qMm9+Lg_3deVI|LyfPYYHvWK5tD9r!Js*qY1**?0Fpx2i*1C;t;xDM87*; zzh6|EQNSg7{VD;m!#Eb;IhXW2@fnK(cX+TJ}>sYFylu32Ci zZ7H6&Zt-2Ri9pPQ(B0wRreQ6*vfx6XyT+m6jFt(YVrUiMT{J6sFtOG$LXkA|ymu38 z%30>tZwD2ZE6W^u(i%EN<2G^jp6~BxMAQVWfUCk4$>V>3*DK}B%`UAQ!{Q%LH{PJ} z&+((MZx;f-bCibbYoj8L;wdTk%#oAxeUBaAE*Ulm*NLo9Qa2;B(`1ttb=FmB)YR}G zS)yoamDl67aJ$E>3a;*CrLxYPAdkn)Wlv-6E(cYN#M{AxvhR;esx)mx9jw@O5`R=M zhbAL3RD^*g^9Cz0Dr-HV@5_-$_HkhQg%M#2+sOc#2>@X<+Fhc>)nc;`RO{=T!V zz@VLypkGP^JWAm$7o&q{)*Q_kNi*X~?@U%ItqNTjh{VzRiYZx%)lH!z(h} zsL!MALFli(Hm*Gsh1c$zbteNTs*hISJrjl+NUBVM9#1DD#0*bHbpXM{VLw^zi$tquM>n7R%TwNFZr$?|o zSN<^U`iI)L;kZ-o6%WNODcF5_H0H9f4%z*=KeYFs@>Om7pI-6U@&AP#%9=pBO0VTj zZc#!M0zvvzRT@CsWJkE_zPs2BhE9ak;quizUNgpFlkt6J`$leHdZ2v;QZkmuTv5_Afar83ng1CH2l`g%!s>fY-dx`#D?@m(d9|z`wr8M2D zHPvsqDfelV+mER(H-&Ifc{@X&=ARh}ZV1JInBE02C4ZjE;GBLJ4YBKd&NyVA{oWj` zP~kIesh3?naY@y?)=LQGZYH^VYGWjG%kAwr467USlQ|4c!?xqGpz#flI=t|-Rt|{@ zAhHA#5)wGwfh+RwzrGn)qifo>6xbvbtP<~7B+((LTh~wbi&uTU$YdmY{M5*G<(6#i z!p6wI?SA^f8V=Lt6CJ`y=SO+7ndbZWs%@5rfd-Q+o$>7(1*g%c9-GY-^Y_^O2uSby z5^7@aSMP8MoRjp2JU$$_5n(zX>b4luTHMdH*gv<$)}`bQ-JWDN5z*!b!LUEmI%fVq zs@^;v>bU#+pUoHyX6(B$mQ>2viI}k~WvMLLM)tC1mt|%Q#!i+09d-|tA?r%9)F7ub+cnt zP>ukn9F2dGs~~YXYhclCs_6P!gAPf(-^BnuW zdjhVQQD)AL@=sUL^w^;JTK(SqhF>jb!4IF?8}G1TK0XCH^hNg7M(B_JXlx-VWvH|s zehFu2PHgJ`>db3yFT^<$_TtS|8O-v?>De<`~5+C`L9eb|E;{x{XGn?cdS$M ztgLVI=TlUwqyhNQQ?qYB{b&`!*SL#nJgAeUDt$l}O{`C{%aX+-Ut>Sy-j$l%pB|`L zty{4@{&j(6SZEhxyno-e1|Y@qmZ^q!k$VE_cs#fALM;RYw+-Mj?ilC(Qb0KV=w+7@ zM@%eji~VKG>OX8{Tw1_zN6L$|ce$A-|JRS>u?39N$7J6=b2%~k_c2^^Y!9xERcl#k z$Y;L1_z3hZxFIVXl&ytfE^Qgu8wm1Ta@Y#)$w^aBMs#)WOrAuX?i>AmPIa*>3w3;N zXB1NLV?BDWK-fJb!V?Jb@H-!Z@N5So3>6+%@IGj6MJJejS$I4!QvZ5w9@lK40#a5JD83P>Hbpw!2I-&tu@qzkUqV6 zvE}9v9wwI%{DF`*Py9g9>bz#0^iMa}cL7_xaQ(;?Or``ps9<3I&M$+unHZGb?mQena$#CFgK2 z>KtF<%p&!#5EVbIHbPDMRR23S&gTNiR zZaa<_loJkq%03Xh#DtQ-au0-0+_b_s478p!X8mZb%<~e=hl3x8(w_t_ka|a?eb4v< z{B>L%+rFeItK#mx{Zo<587J!pfP4@Vg7>9KS!v!b=5@Y4G!ruORd-{`0|5k-Mk2hA zO5!)Z3k#GP0%4>bJ3yjj4gUBlnP?&c36A>x=L4+_#AQ`#b?}m99u;ppe08w(cMN7F z?)dlR048~BKHVG^6*1g<_h13z6XD{*?FO8d5bMislIl*Ja~59n*b{o{l(Avk>@DF z_v${X4Vh|-KCs-&Q)KbJ$b87uQ4Jb+?s2&h?P%MtBgc(V_|DDnotWnM>W$BNW3C8zg44H7E;C~r%H2p27DVlj zyG%7hhLFDl#pGgEv4>|ExHtlO0Z5qrCEwW4s6bYFu1*D}M4sAbh?u91NEn)`bIxwC zw{o?6h!FWC}y_7b$Za?sN?_S%sr8O`-CZ83(%MZOV z7Tv(ooR9)R!>IV)QV_zF3ZZtr5O=ILRVG0Ac4As5&eh<57PKW?lL~W?xYGat#432F~pc~)! z0iTaH&{;sVJdZ9NApUr+Cdfhv{?+$4!MDwJ&o%NoT6BS z3FgQ&4J|z?TigTSXmt4XiSL%Ruzn0#&7dR6y+N$$u3R2;O$3hRyeDc@El`vTe8`Fm z?EF$;l6E`p6YD$11;kSz!;5; zcv{e-=&OKKGzEwt@XtL`RC=5cok)lZXY29}xUQ-nTjl6{km^naDpfJb#t3jp*Vlm{ zfSedB{beQ9c8XRNt;t}Zxy6*T{rz9WbW`j0c1FU0A|c)E!&59D4TJ%Ro=a&?e#6gb zJ#m*2VgZkEAQtWE({bE+R~a-Y7n^}xgK~siH)a6fIyEY_$2~;dSh@}yws~W^+R!{v0LI#WwZ;(`vXBXjeRP!G&2Um~HZHcfN1m z5hrcBRlS$;%qGa?wGnsJ!W249^aT|b(NxI1mS`OF zyrxJFF{_x<{0=Bt+Il}VAL?XzV2ya{P;l_WlKz%zXf59~$dE@4Ppi%JmLXbq2grj* zf&D%yrzl0CBV#p-FG}!x!BzKzkOY#yM2Ah?Fpq)A?`hr}2;34)*nx^A>(r}uvq{v{ z%s5S(L5m6kg<5-c5nI1=;xumKkDH7ko7~6m(>+OC6x2(E6g30nS<~EmxgY;sdGt;& zuk^$&0muRACpe?SErbeGMWBnp7dUaLUU1$1KTQB8@9RO2Lh0`-7|nT6g5Z>TB%Z@| zMbrhhYyVi?)zh^`DjS_Y7%#}h^4ft70OHkv?FjPgtn*&9TawkBBu>CrtD7koLWuz% zxL9MGdjGmy7M3X8DiQj-^m6*uBq|@IsJia~{A?MBrljRu!wJeeFG(ugHp(CppoOCk zwfWdfAWw7fhmCP}WVj5asXU%XgA;}kgA6NGug-#;$2bmFwb30)T=`n*=N*-_NT0&< zp8z=kpj!5#)1;K#nsfBoByeucT{IXMF&hQnm+fCZ5gSGuYTbOY^WZh-SCsbN!ZZ}g zr3$DyjJ-TN%v8>%y=pRO&=_eF=!_e>v{A=wuEY%a_Tmn0root`g^_bg-^7m?)+oSr zg&{yiICcNIR0b9x{8Kf$=MqJvpkE1z?U&f+TCMOth@Deym&H$y6_CVLW?1f}Mhc{4 z8Sn%EqP8M2A$h@*g(i@xQ#T0F92iVD_v$=bk})~qYgM!~3YS`QB+Hu-t+_&s8t5h_K=IGRxCRL|X}d2|8d5QLw)TLCh9B zmu0->YDkgYpF7(GQMm0&PyV;$sWE8h zQN1A!k2a)6FUM)A)K`FDOQRm3de%4CYiLS+Dn>OTnSmnbGFGU67-lpfSYaTC#+@^F ze<{TB{T$viX7RbZrZbI9`zANkBQFEB&@%k>zR;RQiY*aRpT-kIVAHI@LXb7fIzOBB zwnX?Ef8!f36JS0My@cg3ui2)>Q~9V~-d^PKZhnl?*{SQTEr((2Lc|(ArkeRe#`ne~ zevEJHW8e3c@uu?m!JnTwc9jCHlwGrBZiO6)@V2)diPk2_BqYU0&z=XP!!dVX5$VPW zAk0h)ZEk3`g%x(&eOnb@sDM}?ly_ChArrq)&h#X^tBye;j!fS_dy z7hw%G&}8i%?PPaZB!0N$3yisbk-W4uq6AI}*@cc`tc8FzsZ1fQ;W{YNnZjLZ8PPW_ zXDX#heZd7_aP<`AASn`>lHSeJTbxKw5Uw6QB}{Q2OPDhs!d#?CAZ}?dGGuJsfZ5qf z@uwWU*w&!_uE2K+9%(1pfsjqF)tD72gzG%6O~SNujBmGDbi18dqd#GyYEOr_V8j@bNBe=tdmm#NjB=2y6;-lF;q$@RQ~YO zRy6H-)PbAopXG*?5+?c^v8Q%pvXrs#BAb5;hMED`3;13RRr!wJIf?zm-%7Cn~ zR5;pW1E3V&=l@Q=HvjS32WH2)i&BB{6})+mCs6Z^jQ-K*);xw3%f?S{yjB)4Jmm@$ zBeZr&^HW&z69Aaap>Gxgf79vNVXG}cNPk-##I1)Cc_};Vsd^Vp3}8jwY;O}RGi8N* z<@JoBzA4n7eV6m23@6(yt7%lDomYR*pKxWa+s+fKbmTJiRUNnlq3>ULB)3BP!jd1& zJu6ou<>d8k{z}xy&)2M9bPLrGmoe^$)-Uoo7-nU*V?K+uldCzu%gUqiCou|0O@8T0 zcTqQt0Y8Fu&HDmB1HC5vN?n7;+5vQPfLt+Dwy8-*C5qkh;CW z<(j0ad_Wlr2Qj1hMp{3q?M+pyXLxgi(Q#!S13Lijb|RlfD*LAO=#KtYHRf{}LP;~9 zjrEaFQ#spHy&Ip$&7p*J&W6a$Gdvc=IZs54V}jTiJ=6IZ9D$k~8`+<9ubv zsB2Eah0hyrGiLKdAHoGALnI%0y}!vW6a}CZ|D;jWdX zCz6$W;6T>iF2;^E_`P;&jE_#rSI_8$lYNiB*(}*64FK&tBGs4g{35oHox!Ny@~y~C z7sHd{sskhVAcIo<87JIZZb@je?W{@vIq$r3N?12TrhH&^Q%C)cOZwwqGqK}6M%-o{ z;d+6ptEcJ%Pcy)b>X`kO<4MBr{R00SQrU)I7?k}A^DK|30>O+fuEr^0pO4;-5wT8Z zWn$rQoaVjIKbmz$uuzf}jsM8z)EZ=1fg**;R9z2m(fFdVjBf9Gy}uLfugUrnH>hbdT#)kiMa5wuu=!nkS9$yw<9~!83Ki# z9$w+rOcLTm8k}+_w(WAK&RawX51X7$HGuJRNTl(@wHJr%(?3#5ch=yw4`CMPAV7&e zqA0c2L2zYq*wmKgbRGYhPX3Ey+NFVBC90*q-`j_@tFkV5i~8>!tB_)SechB=Zd{6lSl=m*8eICRt*&8+HY-b>o-|NOqq@l< zJ(AH!*XULJNF*hg@8gA5W9~1iV({C9&;ZZIRZq+*HngcNPyzQnv_Q2A^>ydUdvIX5 zBgixTHOnGNa}M=W>biTGytp2h-fa6tAK8~e9f28H^Duq=;+kSHCK0!q;9wA6h`~NI zp|}PO)`basAS^F8GvJ5lJ6|Vx2uvF=8PUKT%@naJH$@fmrP?e5ZsW}ky3Rb%>ia}L^|q5RJLjsV*$uZ zQdVKD}mSeJ+;4|7o#DG`-9Z9k=#f1&_2oQYv0-#rX#Nohy<2`{jI` zc%%#WC1)aRcr?L@rC6DuDk66Ls6x<-E=v3rJ;g$)I%-IqTHzAUh576s>CkddnjGBF zO+4QFlf1N_=Pn(NE{hWLjuAA64(^m>0&J15z1^2w8NY!M+8nq@+rMe4PDflMP0M6} zNX#0l3NAndEu+eoaZ5SC&6gwamb-ql zCFMedNz3J0?Wh6SzI549F%K)Grc9&dA5au;mwN&zL5znR!JaO@SE{g z?!kvN5K3&i*;JaBXxz$QMI|Hv%JUvft52<%U|T(X6^PuwrN{9GMf!9KfSz=&6WY3w zI27gqs4JXb#ymCWtCgalQZ@PT+8_8Derp6ek^S4FRb)$#nCMH8AjriCXU(LO20aY# zF~s7q_N~47pibiowF0kvcwN*5;e^7*G=M2LnRX1!c3yqWh4DsRow|g1!k{#z=OvZ+I7p5+Wu|ioQ6Iy+u5MCp$*K?QvCTA{>&Q?)#)Hy)Yrm5a5{EXx2vH zb$Jo}`Y(H%W*h}D@z$=$l;L?i?M+Z z%SV*xYm);zOX!Llzy0(iM#N&-lbri3qOCjw4`wvMo}ep&h%#XmXEfD)m&Whb`lkB8 zXZJy|RNK4RYrk>;6Oo&#n3eUJIN5@2wvZ{EnP@GCFcs>r0*GbvUv|g2voSId-F^m+ zhzYe^*l!9LOJ6*H8*Th`L{~rLiQ6?6R1Mh2v7g>>mifwG7+b$a(XFwg|A|xt%isqG ztM}*cw(RiUJ;U7PWw1N`Yg8)d3b8Nbw;xd++`4EO?~Oqe%Ig+IK51Jv9FG4bn2CCj z*=w?-IPB)sj>*3R_{r;YX*=qr1(OZpZPA@HeCE?;HK1nK`?*$rs!@m?Z?1^v@`t^f zDQ6vx;CrD;oJBZ>huylw#RDB7A6tHXYNYB zVsb`Ps*j$%=gH26GqLNY5ju1U3zy%|GoOm&viO+C7H|&o;Rgl~F^SuYq3=8$ZnFrL zp(`MpbCh!F;=t>U4raclM>dCVP|=MK&A2GxLi=lH1?3Qq#Lt(kMzK<)9#d83!kO8< z2*UhlfM4hQ_C1O?(gyEKUmxT+b0!V%?abG7)P$$mIkV?nU^2V{=^j}XfbB#vhKAVR zPEnx9Kv;#a3o8E6B#%38Qj|)_UkiqCk_qPB&FS04zO(dxU1m3LcY z0@*DJzTs>*)~m?cH(||d!F+bSaf-&IiO(NGl zEGxf(Xt`xpYl?5r?0(QD=fL<3fDT<{Mij|BmL5-lLqZSRF0`Q%1(~VZV5StfHLsX& zB{3}qH4^{rOWd0Xl?UC@bqYu{iUeC_;M2W0jgMy2yA&1ruI;9vVn2Wg;xK*>2A?U5 zGT~|vhXI_F%8Fy-R@v?_DIu8!)vH!q&`T8pv%vH{8j({1(fX0AUt7|5qw5^X+k>F> z4xX|*q~h2vksnxN&m|21r~nx{=?S4L%)vopE53)^l)E5C}1E2ZFR;A;A{V+4xq601Y3J<&)FY$j?XJ5hSPRS>-~}FDyX7V)Gl+NC6GPTn}l&EEuKYDQp$l1=3$@Mawb7OdnKf7@Do)GC`2I7ESZwcGZ)J5G$$1Lq^K-@UdOYV`9k zSZuX-Fju%}cCx0iwWYU~gH|0KSuu^3tgA17K_4OT9qH^Ig~h5Iv)mzy?Lw!EtsZ{6*o8_wD^lL#&ik5+EiSj)gskS1 z2lEyE+uNN%yYsKHZDWafeDJ`s&GQHsI3IC&HV3HkzB}@&a1bz|?_>)__2a}>4ZiGE z5en#R;2Jd}lM0&R9YGqWtE`MCk9o9;39GkDh1e7?nPpx@AE5=I2ll+(um-|&m? zPEjWt=yJCMfw_oF;LF!e`Jz+>$^^f&W9K(~uhZohgl1b4XDA86`#8YInnGkyZrpg3 z|E93|`Je%)ZOE(#wCr%Fg-`4al61RrPsxryt( zvH{6FrcZ>}H*n<4U$GbLjTBp7TpQQAXvTciiYjo|P|&mXkO`dYIa0B2#D~Rr_4iRw zsI;6Ur31UWDGF_i=BDo+uH-q*bLj5!IqA9HpVN##7jI0Qa(VW|B}o1y{}&Wo3~v6I zP42cXeqLue()#c3Z(94 z`>|cCFI#zhXqT920{|q5MTL)*^J%_vyqe4w>~to?Zvx&f0Q;?!6zNsT@kE}vhU7Uq zSaI@NbVA2Q0CCtc@W34V)f@3$^d&*6v>tYRK4lOu&<_*z6=%#d z(G)#TartEZbC~nvquMyJ?+ry&PN-{vyt0i}`Cc-O4MDoP6S0%$FFJ4nNGB9|>73ME z=3DhYRVi5C<1O2&PgIu~jC!ZWKPJ3w+$|kL#u$cP=qO_XF-d${;&?P}w~?>0b`Ok_ z2zZfn&4e)pBYCe-^dA-eOZWBY$ZetF*JjyDxkH}Q>3^*a0`J!QDLVXos5!KT*K2*e z%rWrd+rDBQlWqBtfjo@Mk)LsS%IOD5QaA^I9ehQb^U@+jY#b;hO?me zH>_&Gxv*U!{Rg<;08`kV6&;x3>9C$L1IbFAZbTdEMTyy1HYyGKHR=4o0^zB*Tj1$8 zbg^Q}rR2Z%hj(`Wi^X4YRmywDD4r4O`Z7gLe`G7(8yq+Lwlevv#{@z-FlC6erOre^ z!3QYs=M>a_CqjE$tO6A*TER!ftZ@vgxq&$_eb0Q zop;XP!F%4P`~B^DAHSl5@#X)(`0YulL!Mh3t2L4TIqiB}iHsnA7?9X?VO-AcwyK9^ zp!PKX#cz_otJYp=l3DXh#y&D&5$c}aGhjjg0OVpX_9fHSShx*Fp{5O#Q}|J7|Fsrg z=^gtA8|g}~;EsALz@`0OpGPGn@~?!8< zlmcd}vb%pXlqLWLEbyrI)xwq86YcK;rJVDblY`k1=xv3!jv2iHv5g`@P~9H>r@ca+ zzu{k(-bRQkCtEF(&!U=nGHB`mn^$1CPPM?tWjvJ$5l4#i&bj5^qhp(a?Xpf#Y` zcYc6&iv6TA1kf6wl4V9jYc1Jz zYB?GymlSBQ#hzz^JDmf6QYPtIxQK&pTh8V41e)`>m=a zOfzB$h`8Zb6&KWO+RG#92VKX$^h$G73Ne-$48dYa?Pb?$_v_}tFnM>0VK!gSt`AZe zR)yw{YnqpM)`bPtQ{xqcO=~(Y=dlB=2lk#)4q`W$DEU4HYuGXs+6HCxHpKDu)9~%C zpL&Mx6oC>;k%c=0N5#EKd)M71o?Elsu3~^cVqRyOH?!LYYg1MPpo&EUoPWz8X0n#& zH47f81eC;Zht`Sj+@>G(6slqzE;u6TtQm`ddUR>9DB>VNbQeTqySC`l`I?W|vc4B0 zf-Kyr9E$UEpL=^9#~hDt)9pN}p5*`WLJYXlIYRE7+CWeE7U7 zr=d1^1t0U6O2{LcHf83BUp0ODefKv2F<2qL_A2X{+89Ksxx)!qJSeLa!~=dND+SP- zgj5`%y!aH%#lW?4vuD+K2)E{~miRxh^%pXVVQ?$wAG$efuJm|PVhr>J>Nvu8(7&FeLOaWGEPDqLfkM9eXP0&)c#PYDZdt!hh*u(x2RVZi_{wn=UWa0*uD%28+ zu+@=@kZ7#tGV2I1y4bMQ20(E$yU1KO(@KI%aPHpy$&YAP3e#G`vx;Ae55q`umVRxt z;;re-4k8y_jnydv-bo&ASfGSDsB?<7Yqh)HNgCJyAqHjEH$>^2`78dXNTS^cEIo?5XGlxNeb);jz0=&ME!{usJW%-5~rUN z^+u2D+FszA6t!ck#_u zoSu!Cy+1hk!qwl_?!WML`agpRNKpIFwJ+O#_2yozRGl{w(oBjbw7=|BGuU*?xrx(62=ArMBt8u`mundX4fa;tmMbdH+$u zRhYxUHR6TE`$KQx>og}+jPQn@o0q{IElLv8xm-b-y2HBM9Ik>sI?9riW0PXGao~B3% z8`jV}20>CFO%?=9gabi10}rwiqYJ`fTH#5BWy`dsXv zcWvX(0@^io;e|EjIwqczAQ;!POb6h+PKq09RJ%s@WzHSPA8oa$%1qH=b(Orr%)E8n zf`@b-iW|xK6L1|Ao(<>bGq1PLq($nkp4U_^8;FZcE_G&xgpmY}C!*)MBo9 z=1pR-Ja*Wv>b$kcC>tA75@j}UhfX)d^5-$qMyrpT%6~D5vW|*7_62GYKRAp662EAH zOH(C2FVXL1q>7f+rAwa6>F?>VaWFJ#g5FUZ9TC+@4VW@<{2e$Go$Gd<5W`7H&@37+ z(-K?JCFJq8O=c4N`)!-sF~{~FXH zaz|-Ev+WMmLN<>6ILjbEn0yUxY1D%kP5mjId*a*E#y_ z%W`WppZ+OyHqzmDgYT7BxrJ&1QoTP`3bl;#(((a_{|hSCJ^4phM$ABb*r4CN1j%~^ za!1#`xP5+$;92|YzWHwVuWch!%HG@Tzw7?^Pd31P*`ua<9Z_l~JN3mcLGZ^dAB8WC z*oHR-T6r*&<5lgPZJmH!0OaID9{yO#f%OZ&S6=Qj2SB2JHQDF{&vT~pU{b3EvwAdHh&RMje>jSJF1Hin(44fk~Y#Ge~oUOacOm+uXul=#enD?%aepuD4A1KC8f8+ORF9{AlcN!oKTD&nmnbX z5bY>T!^3~lt{@$L6b%DcKHia$X>5bg6!5eJ6{;PtCd|NoQ_3o;{V(9EFOHlm+63WA zIaV`1Zi2ExD83uf3oQ||9J)1FMxZet4Zr#it{-CtplaQKY$1T|uGd7G76iTZFF>_e zx?UWr_TAm-Rlxu2Qj8pafs*V2bjk%FQB?RyR?e&swCurL`L zgi|nUCXSt_o$zBI6+%Jq@H0A{<3p_-VZ?I0`5FoiICN18$rnJd5KB>+AkrbHBQ1lmb*exS7*2lX$zpGlx}%D$^r<4i zNaX;Isy${Zza*V`NgA6UeghyC(x+}BdrmsJeLLFEyQ3yjYy|;~bX@py3KWCVVb+z7 zy)7W4_E(Yb-8iEZ{p*SBq<69}0c_vAfNV{j+Ygfe&X}_H(gBO_esq*wgIjqQK%Ce7 zhX2qs56pI8@Q)k;Fcz;}SZY~i5ePskK7#^efEe3zxP*RDX0)*yr+0)bQ1wjCozM!M zuL2><C7>>F=u&{rgCtbLVCHD!Cv4#xITbL` z{dlta$lODlIlS}A#WW_Co3$^eluwums1d91;ttLJJmT|k$DIfsUZVX0g{YQU)xrF8 z%z#hG-xtSvaq;Jn-jZVN;muJ-onA?2c_8onyI1!82~20A%o6al2U^Rum5*uXA>Fj{ zPw-K5ek}n4tvMb&EM9dn<*u1Sl1wTQPQKM#hlP|g7Jl5ihsP2i(Rq&h$6wk=d4R>9 zRS;0*2^IpBZ{n1KOmA|h+l)y276Op)UlWc^5F{5CxlfX9CAp>Cm4`3p|MAGFjR&B6 zjN{vzs;g3UP7c4cMT<77Zb<$!U(Cv9F*y=pKB@=bFzVbHy)ux^rHA zJebD(s;rppiR<%u3Xg*e3C=+pu&RgzqV+@>Az7t@Bt1;R~npS7QQON!Rz(&d0K)~vWx)%b7?dfC82t|OA zNnXwtgiZE#8_04&+zH1RQ`~N@%Kpm9w0A7AM=GZ_m+qw8zydg-kedB#xeh!OJQeW# zm%G&>0S0of0fGnC8|NDQwVL{KIZv~AH=XD+kYNbN3)H#Jk&ke;58z5TIXIu;&NQe$ zYA?{2)i_$x|8)~HrN963Gco1aW(9gyTrqY$B+L5!ur`S|Tp^3Sy_4{JS)&d(80t!9pIQqCNhV z?>X&cgcd8A`IR9*1Y1=XCbxRWc~>JdB^*HS_iBNGHo2etvzF5BX-nPwHMu5(mz>3f z_R=&Tqv~L~^j)O?7#O!fGeY%Y+U zV~T2fzcJX+Zi{HoK9#~e3CH{hAxP>e6F*BdI&$`@2Et|N-5LnMSDyOGk%uxjhl9Qp$?=A>L?$8n!eV82m}7Hk3wa54myu3v1iq9lEQ81%caOaY5!7 zgPAG5jbOgAX$u^g-$qfj@RJx8c+!AYck~EV_FVM%VT95nv0Mgb_g!Qt^%=sqIp@DX zb=gI;-ndf|oyvX}EHkGqpW~+3s?J}Gf}zvCf9SgT8ldec=8EWT?yY4&VBBO1f~CQ& z766u@GYTcYB_9JMAOJ`S*g{v10S)^Qvz0T09x-FSSmVN|9*R3HMH$caUDuHA7G2-#jM_4-E^v=(w3xm!&n$g{jyWDO|oqR9&@z zYu#8MyDf;N+p1eI{4$;E&mv+P*Zpz;i_+wt6uK8i_6iXeZwvtoz(TnZv%aJERtyQ< zhdUk!;)D1NPD05Hl2MiBx6~qx4_cdtm-Do3rK8Vcqha(Gx-r6W@qm{q)W~SDruE_` zCk_B^8?Nh$WB8FlpK3QzGPr zl4-(-_C~Ik4a-Th3;K|_T<|;)q57Go7~L8-LY2>P4`j~X19SzgC+ z@ZuQ^Z&ao+KmZ$5-Eqrr}+J#&OETm{sXw!XM`2v6Ayd%6_w24L0X z!?tQ{DgSzMAQevBAI@%WQZ5v#+sKa(gLwi*Lr&IeZnvCv zmm;z7!J41Zu^GDKE%lPZTNJ71d<_8Xcc?w0vlLfSZq1aT%t(NAeUw}L%vwYFlKIac zX?VvdhVoyN=nx1N6VsUYRv8TRulgMt>oxzFAK$f59gUQ6_X~hUZtG#m5c-W36|v2x zOI8jcoEMHSKQxc~E?o}i9=xiVfwIa@(@iT{bvZ5g@g`;aL-DCcTd1cC{@!tvht@wO zy_>KG<}ay<>Rq1P83dwr{rY+K@HxHK?9p8pF1yOD5$PlV%)13sc2=Hbre%Df9C68DSJtyPrj5gePd>*8m1D^Rd1_3e@ck`c*c~eM?Kig87dXbK~ z@%{{BNPh^sOmntUUF5a60WFghISo~RAhI6*IA*b;1Y}dqNvO}BZ#$2xto!=%Z zNP`pqhXrU$U_#)Gq0Q^yvf&1+G;t?uDWKU7Z2>>d3(5Hy8W;$P3IH)O<9WEo(Ve!sI zI2uv>Y$=R+q>+L5#=BHgn%IMCv=FvLL=_oB_OK#$qn2*s(ij65Y0rUfe(ldw)XUF+JexyjqC@pL zyKZ}=b3l-Wznz_4ph@>iO*;O*En;ux4a&L$g!H``^sllD%<~4oO2JkeQh`R1+sW=W z;rYf2)aI~*{V@P3><`DWF+LmCRAJjIem*DpiHJVZ+jIf)cX=8w-+@i0Sd&@M{W2>e zB@}2^y4+U)Xo&Ztnw~(Ql2nSQ)O2ysMoOQJWWMH`(<*_x56uAeG(qv@IKTW)8qapa z#2&bVDyh<%(HSJ=n}VU&9es<>S1yg{ zB2?sav{MRV?l8EcK)c2Gl6x!K)|^1Pb@r`$@oxX^l#8ReFnXZPb;aivpKseW2-wuE zb}wd-hy5obF|CBRsOqEYI0EVkd$0)rg#%-3`#aj2osJyk`(TZYURtU4CE@oaD8FKex)l zd2}BfFDRfJLDnnzMKp@4_DL=yz~b8Pr#>V7>`|;w;qRH?qbXY*c8X>;aQEC~LL2IO zdr&RUJ!qqoOvdE9SIk}`Kz(|I6Gp(C_)&svSi@O-!+E}-*!A(AHTb|oSh*_^KHjZW zdPl|zKwxCcq+1k3%=q4gHUGQ@*KM@j=uSDi`^AJn487)9FlEqY&j6q!!q<7)GP`}q zoxVac7a-SxT|>-#X)4M&^YFz+QV;b0YxYB}{Crcj&;9?2p7{=PQ|rY*svxRJnq+p-pH zK`N^CDbzO*#bQ$8_qp!c7LGUn6fMiIBq-IpI<`tk=_FffL@IUjVbmB3a*Cxkq?{Q| z!5tfGh#@0AEqg*rnBNsCqWMkGpZ2e|I=Ymnp19W;7bW0RRnlv;e)H34upGTIL2mP0*iA2>MA>S*-)ApY4t)QJ zHhl=T|9snrhgYY_bXeWW#eLf^`W#&vVHcH26k(gLDo_HT%g)2nv$%SpRZRAhc>(X>F>yW0P&Q|I#e}8$|*ilb} z;=3Nl+lsV-Iu_{Yx0fFnv~C@VMDLAEGj`w9yv6 zBiSgD%o9~M{CxcjItk~o5&R##ZYSpb_n}ma8`pp5eHHio{+^rjajQm&k^RX3GaTSa zwMA*hg@M1WPf6zp1hofYk3A;5!LU+=0qTn#OWRJ=au@k}WRU|Li`~oHEVSR_1LpJt z3Vg2!pKCObiqK7QU?g(9y8lzglt306*IsZnmmrO&^`Szws}|mo&xeepmX^MG3Ulle zzS^Okm!1|H`imRoW^+sMogxk0sfv^!^a{>)%-!1y^RYx@-Is4wG69`1 zA(WIPLBYT1KYpDAK@_dV=Ec)Z$kwXAE6}J&1sTl~2|}K1+?&6Ah*>s1heOxKr3ror zW#fsoNZA2;1wskIo=AE7BMPPe^S?6CMWXo(Y6V3YQ8zl*Pizte%Mo6DVvv zGRwJ}Sr&+QQ1Upu8V%!+Ky-4RKxbg0_T$r)yO&kXd2|$` zW+(WmXL?6%rP^+W=Fk5+TFAmU{V~7MQ*&35ZdP(urUO+G0YqlBDFEVsW3KvqKvmMK z7172(j?7Qr<$x>oEY7W_saNbk=?ohdS|26rFfq6Jg?3HN9^j_mV!|Yi_vfIY9ge;8 zmze@NAXM?H$CF%*%;tYPfJm};`~)Wl4X_s!tC^=YuNvJrx4F&U{^Z@?{sskG#*Nt+ z-;Qc;GHb@iJihDIw;B{6RS{fv+5C*mIXSo7&^b;lC6=vEWYvPFc)U?8irzeWyiOwx zX&R*xx9P!^kxLIQrkC9hAN~KO)mQDL_tuUB!XnsyVcO37Z(Cs+VGJnP=4h_3=iA;L zrV~nig8~l+@xHrgHqMH!h-sswz?;{pI;zzv@kI=Ofr$*!IzuN`Vx6NysXJ$k%3ora zRW>J@e)4NR7b-Bm-|gk1Wt4KcvtBMW5D0NH@a#J}pP?Yq6XtP+6!aBqUw9eTY#N$- z%OA75(-}AJVKE*%&=1hZA%HgTYc57VzUBCz5fW~#jrS@A1yXqQ*ytO!=>B(*nQEYqCLaA*jS`B504@-2?QqOHb?)(Dbay*P^@v{t_xxxoB?*m~=@D1+~9c!33$ zl3hAwX%M8PV`(ImP#Wn_8Uc}#U217m8c`agyQND?x}`e=B?ZCfUccYp^LwAq=lyfz zzGu!kGiT1sT<2V5FVj&Cx-b9aX9pq=(gG&1Z`}<{tW{p`qnua~xa3Yz3Sr)eIB^Xs zbY>*iqt{Pt;r-DeH%wDERUc+3^jZv&SQfGTJH-r#Ja?6k<*ch$I0>GP~UbTfwKyM$%?v?C_@Cj4im0EKO;y1&e>G#E3i|nI&01Jt3 zm&*3|fW&1<*vCUZuiQ7}~-nKnAjzRlU>Fw$*)AaOD!$JEkPqF; zuAZ7J@w678US~h)oE|!S3BX@Y8RD}Y-d7pqE*nnAJI=LB*SHdL%uTPUfp6B1NIDxF znIhtX`?{tdq9BT(2`Yb0zx(oV^>@emFB4Wf*^gK7`H`XIprojDxE3C#^Qk=8oqz$o&wTjC5pJ0$*(y*bJ`zv!0m#=ohv8ew z?eD2HFQJ~&Gy8XE7Nvthy_m8iToa&T((OIEKEiscC$0H zupx=OU^6Zl9;3E=#MHOC24%wHAjQu%SSI4oSY8c_MkJ6!ux30|;2IfVM| z%9d@8c&mnUE{vys@L<-3*)VLDmKiy0OG0{I97`PRuj|BhBqRDOS~~BU6L(igTevrT zYQ6jvAxh%x+Wu8Tk`S})er(ts`B>rpeWnL!f!{Z7#SL^P9&fqb8%K1}lus(JG#24H z1u74dzlv8Ish;kq@i8Cbbov#=mFDEBaeA$}(1k|c4??69rq;!fz6!F;CsxO=IGNFr z88$#Dh!`Kk@zi8xymQ;f1ld^WcAu}YW#*KqJv{8iRu`GA+hptYS0X;Ez~SMo=!;Ev z*v>jNao%M4xP}|f21mM0_PmypyovRb>3AB|TeSQf)M51X<_GJ`B#$oTV`n>h0WMZv z!=gT!Fql87VG<{koVydNrig>^___5T2@B_n1MnOQCgPY?=`-LH6S`5iP_y^8HM1**I7X&P4ZQ{n3cGc|4 z;6M6FoxSq=d%YX(cyAwMmL-e439gTzrvYtc1PDgAop_`WQCYyT{aICaPH}lgslF*O z_wS4D@KAG?m;7ZSiHs(CIVlge1*&$uT)Cb)2!%^o%aO43=YS=HJW#%g+}cliF0cck zbNEy%0zOjSf8>K9Jc%s@sM7!toj8DR^>5hE{6(lb4#OE+2I_O%9D64Q9^sIxSt?f% zFO)rA)TffKd6S$lp5q9b3W=e)ly0W>gK&OL=v4C(!U~ zSgzxoPsP!o;UAeu$VuKD;o&;4kawIj2ArVC3XtJbw%Z%`iF4P99|*zdUb}|kvAuV< z6As&cu8V?W6PWYWDiguATU887=CC0m_=Chh!ij<`BqMO^DFZSB*Jn;sK^%5Zq_jz} z?f^|(S|OCrgnbC$;)mp{aaers9y_?rbp$Lb0-;Sv<^zc`Tu4Z>WZv>X*hk&BE6^@a z!nR!a9o?{Z8n36c9TNNp%Og%c;OIWtx~rq&JCctD_RHnY-)xbpik0RPejnRN(S+Y* zh=Ne>YJwLF^d?&L_p=4RPtg2&Ay7J&PzDEBpq>Gl7&S5kD^xE;52Uhjx`s!3TSZ54BIhmd8$}Xr}gkAQAv|o)1&FG;6Mm~NgZ> zQW(+4|5tQzSHfv3opfaJtDFNm3{dO1-3C~@dSBi7AHySj9-md7O-FhfGxaW&jFye( z<3dq)lbJx*QUd2^mN-6_4b4cx$sV^#uaiK`$?i1eb<^J zi9*0ybR&30BXc#6CVv*X$xna0ycDD=oiz2`XcN2Ryetcz0D#&%vB7s7gG=bKRHgw8+V8mvGXP+VaHP_SnxU-@Jx@$$q(DzH9x+H}&sJQM>OJVx zJCn$TR_jfUt98HvpGGGem=v@F>I^+$Ma z@F7J)h}EpNt8*bfn_&jYB?K?wX?AdeB7jPhS$bL>)*;OoWKrH&auhpMTkv5$I*dk* z83C?5e{AN)*KrsR=L-&=0Go#jvDpX>JN4?tbB8<}nP6ASfX;fEL6`R%W%B|(Mk@7D zSFDK}qu7w=IMx2)${)lkYLzGLR`$MgHKz@|5Op1esp~UNeI} z-0r-0Y>LW~Y~G?kItP{kompVU3PISk*HR`gr3kXB>6Xz`qbXO38e4DZ*+D~ZvznYv zdpR=|VY_@(+PZb-hHm^IFtK=+%NZBSik z_NPTU-Wa&)$Gh>GTdBjBBk~kVZ0m!4{|_wBsi8An@_L2}0r54?cb-suMEWdy>Cw`jOl2sbw9vC_j15{@sTZ160#n+tQl2n3Jt zYz{bS@*qe&#d=q(@CKg4<}aS$4tc=s#G`xVh}Wo%6#rD%!x4q}Y}N~|@MXj9?XQ~MP$;YPpFp1f6>jKb-aPu4*Yv$4L6tFNt| zW}o`QDn4vVPrQovy~KgYFiC@F_K|@$Z*4mLNsI4ii{iZDOk7S@A53ck#Q&kjaWZ80 zT7?}bmiga%2L4!qeh~bDNVisI7iz&y+z!4YTLA*|gLa8bhA1=099 zxapmRBO>&1r(CvBuq;{92P~F~=Fzt|CaNi-i?tQ87({jv&UBT@%-PS{Gm4!?*`=i* z;$2Z;p(*;Ashi(cS;(#6K<2aK) zIxHV22lCG<^fD6tQkgxZ?Lax%f#}pp@D&-Vo)*avfORw4Th;W6mKU&|@h;%t*eCEc z#AydV9MB)1;-;KkgQLf!xn``)+*qQ`zY5oYOf(h$MCj3+oS|aw2Vcorlr>V+)mW27 zj9h0@a6B2e=hctq(1c=5(9pj=dfjz!TGjbP;r%P6`U<3AFu1oAg*~nM>lI}&RV80b>`qQCY=pi! zO$+t`)$3 zmav;6MHlaV7Ml0&5Pw@Qc0!FeZv(Y4*iW&7$I_NKgkd+3IzS`6!K!nIIEvvr`bS-; zY4Oom9tEvj`kP(7=b$WxnIP{6LFG2)hvCGT2A9vK9eXS9zr}ealbfyLuuQ&Rcg*wD z5QYOoXXLiB(%BVs+X3EOV})+UjA zS~0p~IF@+bvq0zS^yf*p!Zn?iB*$AJ&qwVql%M1ZvVCI@SjT-UyHTGaWf4gK;uBsf znZ?IcjRdTETzstRki3ZVU)kgZ#XI62wkfTz7?luys7AF>To1ab#cn-iA;}?lp8Den z7??~gZH&FQM>li3$wx1t+0AoYn{wfF*09hgA@jB4|A!MH$K9(h#Va#PeTKM~R_pSP zr7<)V_Vw2pyrfSvF@2_IyAXq(w{fY{rEiIJZZNlPlq5hNh${XOb)u#9{aRk~xs8zs zp)K>!l)tb!GGJG%;|VV`N0au7 zxAF*+X9rv5i?BjmsGC%kf2wly;G+hwWcu))|C&3yGS*u&IX+CuQc0-Q)$%?zaATuk zqw8hNG=E$2q)g$l#V84gxdQ7^Tp$tLqPmMz?bmK14jT>`g}BaPC4yPP3~g}5EmI6a z?aFZ|zN$OtlOtrC#fr<*)Z{1rKoG0tFp@9nvabZH(@UkOTkQ|RQO>v1q-(RPu%5&U zif9TKHE;P$K0xc*OO&<7DGlmi3=cBx95>%(fjOwRxVaTiRg`}q!c!9elO5M}u3l@_ z3$iE@?JuH8#4O#@%Kt+m5KRZ0^$Lh5Ija~|-4#CofhD3cHj8Pgziy&pDb?e7B$i@< z%$cE?TB>W~n64(#fhtUS{eRpExJF01&!(Pc#Czr0H(lb5V*=A392N8+g`c`se99%Q zKrX^y6Ys)RDz%GsO}nLnpde$#i{z2LJ3 zS27){*?Qs-jsjl9lL0C7oC>W_-ffs z7euJJjI=82F4YwmqX`Hr6$_IzamuZdXA@xq(|f?R`ww7V;`raRiOWn3RPXmlyW#u6 z3_BUYxl-2NouU}40Y`SpQXKNzuq&~{1?gwotg&8%hBGy=K658UQ*C0UH6j-B@0vxF~t@ZuwiAW@pKwHH?c~% z&EQ(8JifZo@luk0L@qBae&bq>#GjVSdOm7E?H)N5bwR)UTXeQmMYPy# zEWFa_ut~Om9N3w(%t(3J=6{RzO^6Nfb>e;JL#88>p5Us=@#)UKepEndZw!A<)(q{$ zETiREe;V(rfLgyU3j_d7wuW%L12WeACJg5s$i#d|{Jj+M6AL5Ps#)#erx1k2+|#&s z6C5L;>$Kf(+S|+BpnL~%A`iV!ll1SHOUd3@<6ne$2>{N{bRCTp&Yb~OE-TkpAS6%M zxaSjMDy{i?0E`h&b=}k0RwXJjeW>#W`8J-d=DRV?*bf%z0<2?982f;%&U0hmt zPFRmnkd@B88{9A~yDYDiwPqbD1Dal}5HdIBRuX|x2-c9A?mc!0WF0*$Z!R!Rio?Qt zb0KB1`l?NNJY7lYf}X6In7D8-T~Q^RHdkpK+W)Vjy_N9ymjNG2pAf-qx@m3S^O163 zz}c2%J>Fi6`9Q9LU=oSo3$)g3mFAYF#}9`pb2YQ6f~H}@^YQ{o%7r&)Oy5WIro*CU z8*rfxde1q*&|FK1dCTw<2o}Vn$O4O{QadNu4vh1sJD06r`y>sz*w<|+Ik&qc8>C&v zcFCff)xYzKnUvl|BkzB(0Mz&ailRFHB<{T;xFI1nlHOI`j$pZ;`m61WwJGU;yD#TW zA-6vuc~^QL&2zbd-qwV;aGRCpl(lO)`kAu)6CtI?GR|M)+; zz=MV$wV@zT_wxCfAaoxfdvGoQ0bODx}xgY4lq|@SK3ywqVA~WN4ByIM@v0ef@4Es z8S0`>$DN$af*^{DipQ&OTJGGguZBBB`Ob*S6`G9t8kZYHOcUSG@lBqu{|NzN{k^vI+sBps;r;H&QxPU?V`lj{4MagMgZyJiK7{G5hD8q( zfCt!V>d2iEwre+z&3K_!`5aT zgTQM`Y4iIWJM+XLA(T4iDg6LMa4cdy#ip_QqYCET}OssR0J}# z+7vq8#Qk}AmA#O+>fvvN=sXA2WQpeGCvavv9+~G3GqNm1{2g_+A3l^>GS@dVyH_!} z*wf(ymz)VR#G+w87J@-=aT2$R9`-)u=Z%FV5PbGf74D^0A)(=B)`MzDO0br9vY@be zw;@r+)0<1yE>#GUUeAS4lm7n zHvvpY4`<+@kYF&YHJWVWjz$9`W@bNLHa>8C;LmABuWUA)- zQW}i~=wdUpi+EBZGwfuGWRJpNGFq_o$XJ)+RD(B;!NjPpCtvBM<5P!cpO+6Rfl49F zmtSv!kH^aY2;cM|QlEOdq*ZZDqz$`!(PGo&HkjVS9F5-J1oBQ4Iz0;cbHI&VWG8x)euHIB%F54kUh({Hr$jSRX*SrXkd809~K}a=9Q-m!$bBt z)W6sdXc#pYK1-4LFJ6|cike9kV#*h&w&))amrdv zM^k-kv!U0?P3L3zxw&3BFOyzU$P2u`Q6rVM9kOwwzD{#=vSr~_=Onz&N8kS2G@5K%^qyMu>)2!DP?AxOU9k z9@-a@TGaK>hH!B$WvrN1zc#i9cw}CG{NkOfm1Q6QahB6%bCm@CB)QY>PiQ|bux$y# zoU38di=K41B);Cbr7C!|Z?T}#ya1}kGwgOwz+C1p(Ew#4|G|%pL0Aj9%J&WDasgeA z>Zhj9HQ3|P*bTJXg`c$45o6LQ>=8g<9+6wcI|xX z!~T#GAyAhMUQ1ZNJDEYF{1Mwk04L>V<`*ujJ~4&T&+-mF3_$FgDqNT!n9OZ~n!-CN zp8bQSnq%8&#qWDB>N%o|n(sAFp%JR+EI7TOtx~M*Q(&}*hSQ!l<;IErkr624 z&J06x@SFtWuPF9cM7mz<0W0$5r4dD9)Q@O8K+9OKqj3`~HC{2}AQ$i%wSmB0%|#c0@)PcBX+ zb7ptlY_gxoY|b=YMeq>XoZ#K07V*8yK6|E3E)`Y;>L7fNm_00i?`RZOg<~Zq7Sd9| z@J&1@sP)EbUts_;CKwlOAK&_C%r(?6^b6(mzf&lLh zD4T(Cxum~%@q9Shn?B$5Z%RDW8~Z+9{UE9!RuW^Y}~w+&9%GXo($Ek)rgn zIPch&rx8qe|A*b;BmESRKPSi|y5U9m9dC3F*taS59iReq?ckmt5DH=<;Z?ZM+}Z~E z8VmZ6h_=;iVsZc}6d)GKRpq6-K86i($LDrZ)ZF!Uq9wMlny!oFV!2iS?eTcPFms5A z4M!0K`H2cWH^DLTd3lh&$c(#T1VsOJJxlm_P`FBXfMO(bul_iMW$nXe?xr=CgLu-3 zG3>PSMqo%uEqk^g+a7BKYGRG_MU!ML$36r5-m#!K%@~#Zbvf~^P!mj~r0ug54<;M= z+iS20+>46lln~U42G<3}?j&$$t~Z77E?`5X-{?&O3aBt1|I~bN!0XOyg<{5L!yGs< zOxORH;LcJ*i>Wzwugl-uQ;T|8S_%?`_Ua7>e%r~}?(c*wh8 zW&YcFl)1NYplet=i&rm!|3cnu4rF3V{~n1Tc~>!yb+4k=FHVnZQ0f;p2u1tJw~weq zXS$A2p5!en?!Dr`92&jemR~9xB}5^tQi)@rk6t(ZKW^RwC#!ad!AuNcSDgTmZpcPtO{U_lu(6l zCLp9mEpW(sum|xji5F>66B#Od8`|og5$UWS+d;oerX`TI4kk&?Xc7~Bv zKREc^5qL=84uh>U1RAiZtQN=_kqnP)eagRUD@DTM%6qSMp!9`y!%DIs9eOUtag*W3ubwMZrbD{pJt*-p{YgrMj)+rnE z#9Z74r?{J!l1A6{h9uyUZ{yzHK}m$~;w{W#a4bD^_T^^(RAf$3Gtj1RR76eh2SHE6G#7d?Dj;02NDo}2-o50dss(pbcZn-|e)s^w`P5?AiyCcT zC7XEo>?Q4M2khsjjJRwYvtpwhwEOD5Q)nw3-l@gApP}O9`;v^}mF(SEU`6H6eyckl zeDm0PKeV1ZnD_;=94eR=i`O#U^Oa~P=zsy|6Pq?ku<&xKpx;f)FqCA#zKs^x(!eXX zV^YyAPHbm^UL@`Q?RKTTXyv6kg4;{U@W(MFey!0JGL_*5) zZI38-GGnFDb2%@#628T>{}RL1-BF2^-DH37VxVc_bWdkjK=$xt8vB1aS~F`Aj>!N0 zqJq^3RJxfZMb2A7H;}~LPVep}-?Nin3S>b+p(848Lgd7F7VX$13^ec#JXI$PdFkcW z06#%I%9fsiXI$+de2qdY`TGtPtc{+%0u_y=f7#hKB5ZTp7pRG9nwc{qLkjiS|;uw!m0;$C# zkodf6K^=7TeiKFFejxb`aU~j4f#^8`PJVOO=YG@1fHXN&vtwda_q9h0rD}#SOtdt3 zR1PznmI9DcLX6nNdPLw@RSVTfY=dBNRof5Q`9%u>kEL%)THj<1sl+4SWcFtda6mUT zYdGFUgXz_@TL#we^v>V*ibA+(l+lV5-+Ak4aK=#gjWBvMB|7yLUO<5tVUD{dKx00Q zCYff*=08{$&&zAuA2}h_2ymyHsJYixXACP|7qOHy(G+9B4v0g3wJ8S#pTh7Z`~~6i z0^Gz9-GTo-#;gvoX9}O6q;m-(Hp-8kwDq3csu1_Ic=ge~Q!V6X0uAMdMm?l8R0B(~ZXU7*;#ul28WSE9*IP zvX+*c%0n~E_b$6Gtupgosjk&^%9zhx6WG8({b{Fck@g=cuw-E~L)1F)JMWu`Z#$il z>@*h3otd{LS=e_9SFmtpahAz3U8c}%^Jqe3G)Szt048mbD75K)62Ds9XU2L_# z4QQgs)Q*w37t6{W$~?mISVWuHM=l8ynJ77@*Xak0uE!=Z7(?!q;T9qTpYkkiO(l1k zPy9mQw0`(8-;V~uL$5=747&G3V%iC2?qn}z7fd4+xwQc&?&ACH7VT$PrM z0sex8B9xLO9^MX2xx#7j*;X?I@iP@ojw4trJEaz`(V{XgQo@vC0*rA3$u_E+3E$!SH+fY?yXbEAj3Wck5s*LQm%q zxZ8?7yU&c$y`gr=y-YXU3x4h6tVK4z?nArR_Gj01%cZ!IE?Ck$@J#II>o0K1e!3(8 zjF4vJ?eate-ix8eSQx8_I3c~wTxK)xH~-W7U7{!N$cuW&h^%Kbk$88Ucsm+@{rqY# z8nsXT1P^uRJSrOe)*dQ3pD7QjrlstocXI2%BRMc(TwG3U!u5t?Napx?#Byj7$ zc#av>nZ`ja5w%qNZaK3(f+}6h z0^JkGfYXj2yzUdGm5)iF*%3(R*Z+KAjuD9Hc zZxCPa&lo2%?NNh`u;(og#)KLSCv4J{T0T9y(4Mp`pj)b|#@OT_2;$>It$1sc)pfqOs_uS`&cP-#*ihsa9nK}LdzV3}lW{~u>MVULVtG-VU&okVG z`naIvx{D4C;Ie$Y7Ak2Go@DzUR}z+os}j8vY;cMdO~X9hzYH*Pzi!R8KZPjbYI0Fq zcXZ>CI_3Xlg@MZFy~K8c%`BP?lfyxpF=>|1Tl7C;2B<0hN?qUO8V>S4yuJ_cIBz?IaRr6I?%a&VOX#auS1**c;rxyJaso;y zE?l^+uFoBNA3qB22!cLQ(uS=nLv&!HLAn%Bodgawd>v*A%3ZBUIEsS%o_sqMHb1{I zLXr#$5JZuUldHM7%pIkBk9?4LIiKYB>O%H9pmpQNNUHEj`rKUV_8*N`jlugKvZlPs z*leSn;0C_> zJT8hv>YJ;Gd;v+uagMwaE2ilbp9hTg=k(RR1rdl)VEsIG zoJR2x@9uO&e&qXJr>)7A$Uu$9Xpq0r5QbYuQiWG5OM1u?!^niDZWJR*mD zrD<`8wfh|N3RWyia`__s7cY6T`S^BU^hvUE!$sDEs)m8~aWoL!>OOvf)L~u_>hQZ7 zYHDtlqwH%Pa(0TXI3Xs8(Dje`>9ZQAgjhnlt=U=w9-jdhIrF{Sxwsg!@uLn$%>0gh| zi30x%<7oG4%nA>WVZ|bIP{7WUAfhThAYMx~FyZvQp3iOMHOCkff!za{MM6VO6hBW! zQOdB$Dm;uLP%n83@M|?|%{7Sjn||TgbTP_z<4sPa$N9gHxS8?wM&)4)1t-PgW;tLi=Y2icsP&p5w8W-nJD`8 zxe+stcX&1SBPw_a;z0xs*uV=Z|GiKatbP=k0^zb&K7vM@dO`Fgr^AIE*#Ui9Vz36)?;14&rHF-(OC)$km$9h#ZKXHGeGo0YOJ!sGnPSLj5Q1Y;=63 z&`uJ0i4A-&e_{TOzy)T30VpOJcrG6C2(bMLbz;xC*U!jDPU7A^BZG#~7~QBLplZJJ zwiLkz_WWHHb+%BVQhGJP!y{eVoqMF)O+(LBFyjLk7?*sI=$?PG5{?`<-5mk({@7cw ztA$Hbabk>>TVNJ8#8bW0cO1({tEp2`drvX=(TU-U_dPbfc>BV($=V7A%K~MCAFMDV zPCFhnvx#aq{DA2KAsN@OSwk)_l}RK7_iLp%Mp8ComXIF=x=K5yWhKx7+tUbQ5~28P zGQvrX2tup-F7I7Piw=H0GiGsn_fJ+G>_#PU*`RV|l{DsQFc>cseY|B}*7f?xB_^uv zH|&lK=KINL#I(v&f4)Up6;JQyFnJTk9Vg-}Wz?fpAKX37N z{910>8rB-M+-IqgQhfdtj*P+t`!{Eji!xg#;>3E#!PLF0YdBn&qL-8QCu*6NfYHxM zV`g&(yhLZ|?TNEyU&>W9&A$;laHzHftcarwEEj8AQAsn}N)2v&-WrCUx9(kDDG!)^ zA*9{d>75OXC(dkoqV>0mH~1At202?A8}Sr(xHi znZ20CaM%?7LjmK6JdyH=lzwOjKG4>pMuKcVT8ma5~~H zmSsiB;15l%re(bQFuaT8h%cN-V(c>*?1g2-im0ipzV+i9Mvai!?at^6{gs7jq&Z)- z?EASC^h(;4WD+FXMMDWKEnmHtjcz|;CaAfK%ub&woP?XwUMbArG7@DGC+5kwgfIv~ zSx~QXg?L+|HIf~lCyDO;z1*jeKFNkYPi&?OAzFxHn#`|$&gW1nmORS}sB#{<(o`Z& z1q<%Nca61mhO;R(oeEJ3HHPJ2E4u;o6EYd4>wtzQ24!1S)ctWb}P3TgAWc z!$R6h+ho#XuR+h!R=f3~hjW(9Bh}V?kX5S_+9ZAf<&Y0KgH~*#zrc}B`UZQZS*Em} z2xS7*J{19!JPlD6uN=AUa(SMVVum#f4c5iU(b7k!ukfCgF%RdDwO!A~Gb~FPgWA5) zhR1!8!l~ZEW}~k3XxY!zy4*VprJsYJ_4}Fyt^( za3c?Mq1O_T%tA$IDWJ&8av<6_ z(7O^FPq`w{#;%}S2WB+XX%^mq!JrNI(VPzY-gr&I$IVLE!AS`JYPz^^_zF4&(n%&i z2!7-QX4KTdH+u4utlLjlx3H95!3EaAI8Ho+f}aG-bp^{Em&mD=x<6Q;z7VE4%)GIy zTxZjk0Q5_6aN1QJ@KcGc;kEX>{S9SQgf?5c(WZ4j53p#Y1tXsSK(R|!SUFb-``m{e z_<*N7OyEM{m!+SG^w?EK8-EggP#&(eK|2`+7g(-O%X_>-0g0WZHbN;WvEUn zWkUjh1BdGQCCZ22xzhM??w^0Af#iwue9!rjUiikyoJ3E0iwZV`^*5@$RMIino>7#W zX^O7(ft}gr;~e^WrDL!QaS7e&gb*Vq@_7%PMRqj-+o%$t@Bdi$q*$?v$( z*n0nl!!v(Y_(l6MN`E2c8b7slIve$ttJeJx@jphNb z9PQui(|S~L23mNP^=SQew#a#yc?*O`Bh-ShpdKE{qNS2~{9V#P+`&j?xLE8r>(*L{ z8fu|sX6c>41A%fYZg9Bww{0EIM^YPjzh6QhH=4p$qa_x&n7cTi*`Og0u~=9y3je13 zx&t^$FAi;=>Urix+L|*YI|X~U!*fAL#uueaX10i~_vQuMHckO?t)S`G{>PEp#Tw zIB>YJ15^y-Srkm5)LHo~sJgVsm+ri<672Id`j$gVXERs_6@h-ooz7@)#;3tW!dD-S zyI>I7j;4_Ui?a{CCFx+}NFWM}1Fv(>%r(+dnRNs%)-}Ztw!Hn~tuajH4aD!6#@Jp| zup;NX<^N*5Ue@?JBNEvOJ3IxTI02drHmQ;4dKv-Qn}5e=)axhnpi0R>I>ZU~FVso^ z-kr8)V~Yv*;?*-n44Q>`i;z+{M(;84XfY7UK2a`DRD;Wrl0N|AhWiBErLCL zDp5*5^NB7kO$~nVIPDPAW|ih%;S&hCR2J#c%)JUL#@7`uFtI)jCVa>D1^c`K^xL5l z#L{U&?|z;P$a@c4`HxFajE!ap8V%nF_CTRCQTNhj2`FGiboZWV0)0^fb|`n*aE4vp z0UJMJEOwU6=pG)2h*5!@&M+Xt6yZgxix#tRC=m3LF&ChNWP6nyW{ujzFxx~jz)Qx6 z&^Zwi#J5|(5im=EXrXJs(`yT3D~lFqA2_~D`q~~@hlUG@^bZjyNd*s#W(oKxt#G=ERLaEU!+{$dXD5tb*<2Po+kjZdtfKJ=y&x;tS#BL zp)Cr$xR5Lgn_b-)_l*a`M8Xagb58+e?P+*Ck?dRLu%RD+nM1zhV0zy9l-;lXg1W zjv7F!=615?^b3I(4yH}Zb~G+A6IFk2aSJkj`f2)Yn(-duRgAD{{$8)IG%Bkca_AbI zvFa2nu*lb&xl?dU&~~N!k^a=;Cq4hlMg3Cr!e2?n^Os+4_&RG?#Uh_A%21P(CtR_M6tcJuCxM^o-mw>=Wg`eONR?xNI<={QG0GNJi36k*|;;U)Ek; ze$8cCuY0Y0(X@AG>L9s*;oA1YX}_WWcD=!Rw^q;({BJ$>k9?p<<$s_4S*^oC_TRm; zS8u%^;65Vp{#fVdLI);B8zd_%MIYbFxt-pX>ocd4Xeq(DZIOH2OFiO4sKKH{wN(2qeF{Vo$oWwXZs49#z&xYodsn-GvN1;` zxGg?CS(kffu2Lsij3$gH^ouQVFuGsoA)9KMzSy<1CF=ZmJl-g$rxH%-p3a4d=y>b< z=*ZWWQqzCt=X`c9e*)g*lV}MNm~*kRF_uyo>CAK2d{ zJ~+(diS2uyfxotgBNw?!^rKI3xdnQ`w+K~z+(;8%_JGm;z*orSqZqc-2M=1l>tA~* z7yY)ge`l`+US6Iztlg4VtiQu|+sKNu-hLU|X)ulZto4tN|L^*8>&tGY)m>pZk`;mv zH7B$UFLxpSzxuy6To$KWZB(#0508Z?U2lhWX zGhYcZvD5IHE89P$kL&3kv;*xw?gpTt^im~lWRVedgW?2 z7$uSITE8u3Z3nt^d3Srg_35iVV~fk!uiIED@n%d!MXGw{*=bAfelCX*A^Ksj=t_c z%!Fe$Ni*BLT}6u7z_w5+t*Qp35v6e zX1Y=FM*lg%fI9Sg8F?PD|IW0LmHl#5=d+%4y>>m4T;9jGe1&7@{6(r&{Df~&$D!@% zVc^^!Q7(-TSGfR|+wIQeiu2a(Ren`hmWvYp=NjPJzXu&__29%Tzl1T*-x$D2Q(avnnY3i^@rS=#qSS8&Mn>NLhB($Ka`W zUzgKq4fN;4#>ux=&(M0@=(6uC6VAsbicb>$_o)2$00h!kYQnoMSS@KeB!&7)kZTL7 z*+O&$g&4yxl0T<&58PK=17n!BPCC>+80~?e)G*yXE6U;+1198`z-s8i-^=8NKN{x~ zH+L8RzQ1vwoqU?R*D^Qdx3-~r4+eYIk!LQ4WiYZ#Cii8tFQq1|jpw_@$G$oITZq^& zT{w9zd#AOyOW&upkGAwH&0-nh>#`yggH+Oy)psswZw3D9iT{1eWIeNW)mmA5ACYM! zR?O^cZ}tq=XRih4XUy`eRo3ep5OtuVb<3|oruAd)>YowhF6|`dX252jXvoAH2{uke z=#>o$yZ#@pz5*($Z~I?hfT3sTW*9mJBoqW@U_b;EEV^5|LvjKbN>C9nNDFC@l9HjN zLqfVlq+2Ba7vFpT-}k+>?ph2C_nve1+4|(ib96|$ z%Ct4{ssOzze5S6858l!S&mAu(jd;UJrCOg(n;DyJ;@5B^mWdlmXhAJ=qW$F0uisW$ zcs0h0M`xdWq|qBSQ4`&UX4X6}X?|!pn`e$oD`otmbaLtS+5K-n)a9n!Z>Zb`vIP~e z{^VB=@)xEnvVsNOuWs1FPSnH~T5vyyEF)xkOc?n=RzG*>o~B#Kk~ zo@Wp9J%2yVS#!o+z40DfVox}gag+t@fDbe`6JIi`!dW(|5jcD5x0P;b8O0z^MH+GG z&V=aL{OS$crtsbt(nPxEqMy@IyHyIA2eX!`!0X{A_vAHQgJsVi5pIia9N25ybKnasNhvej)c^|kQlG}a3&%O{J*S5tH?+V{TZ6!oL zIM-3p0nEm>&)kPmro7%26%jb*qt7?V>OUFHTQRfTzf!=ZJlVZ;vq_fb@wP3mjkkao zcFYNg>I=1m{NX=xuc7O^hZ2@jH!4p7z4Z9xysz?5!e|S8TX8vL@|q)STf7-M$fvg7 zC7g~6N~jeap_f_xi-!|~Ub>kxm%jHu`~IcmMa<@6O}xY*M??M3%iCju3j7 z)vb>Rl-AT`M=68jhT)QFMf0>jmHUXx8mLR-rALo%EO@BS5alzmsxx0e)RGsl67zM; zLPL<`#tQ}JV9P-HUa2Ps*aRn`ktr|L*FN2;rNe30s@FK8=gSO72)tiv>Dyg}F99?i zO~+%4UK54PTTS|Jr#`@W>Y$t~eS%Ncw3!s&u!^WbCSSI6tmwVJDD z;+aP1YVZE;VykS`rV(YyJq3%*e;8n z_06rlN4)RP7Fju!{iNkj#uGhHw*~j#IQ&^-8aqq+lI0j}>)CFsWFt`*9c~dKOIxCW zyT2ARZuOx5l>7aRKQnFJ9;x_E#j*F3mqi3IGDDV^_x8hAB{@sahvk@{gY9tF@=^Ni z5_~_l_56(C@zr_76Q7Td(a;w24i*OF+utF(8@naHjT5br@fBLH{^*(cjkg}|$8GZ2 zFb!^5UXd?(4wjv8cu=BeNfoa%)OPhi?@QzFHs7q^-u1(_RFB48MfyN~C+FubHex-b zK;W#EB@NtAW2OV0d&iO3UL|>cdFwl$SKsGxXG3b8;o~1pR;^R#@9%mS%WU5WTkR zGjef!>HtTcem@m2-uvr!HC?o;50rb0@h+v`fl;o`Gh2A|gZ3%56E`FK;|0&n&naxC z+Pa`~x{s~n3-W^vODtaNJap|JetD$ILH`7Cfxp>&65Y9$f_i-8*;1+aG}IqF!Xftw z)nP)_#^1(GAs=U!r^=1zTVGyd|YOu*e2j}jA?}gj(RNYyg3(g+-eht z^7a`Cn_1|9=)U?wv$>jYctrGt|8&xwUcll7scu!G@OZGBdg=Jlf>-068}mtSH}WIg z33Knbnhm*PXjG5oUWX1!j=j}4>3cndY1M*}qdVpE>xF+mzatt6N9N)6B}v=_#Md_p z&`Ro$E363js@LLOJf|40l$LWiE01rlUOi8~ENm!aipq7m)lHP50mQsoS!%>RRg$CH zs7v$`&=!fj$;&rTxLXk#;TiFL>D!Dcg(o7f!`lw(Lm-Ah|oVR{zRu@N>R&5iWEeW z4r;l1aD$gIfZVen$mPOC1yU`aueHS#0>Qo$7m&M!bvuk&*b0wN=rhBjZV8-tSy%uAU)^7EP&!e4nSt(`${7!ScPiqL($p5-CZ84N@!F1Qz`rw1XnvT=~^pCosi2F#WC- zqtB_ILZ;Zmcj?LWKpo5bAjbSMPdSb@ziYu-Mi+=r5>T*ky+i0ytscNF#UM2(lK+wG zDk{K(!ex*>8#usH;i5!9k~rwZW5)bjWbc7YznB>tE<&Gjl5r65LY0s$w~~;|N>Mq{ ztKL4hAeAWqNoN3(ZUijW@d5}9Ob`Nv;_TOVrg@DX1}YhFk4WQ7OICr&@+axSTt3y< z87oYiPRCiVi{Y3db@72&DAI`~1QUhK6?`Lj=>f699?g%MZVF)82x1Wc!SQ(n;Au&S zbEt5SW%Ydr$0QD;(g_Jas{!whF>v>{{%{h4(gmzWhc55Q>*$bjA$S+an~CQs4?ZX0 zNhb#*O%pM&0m|B6~4Fv9OEp3{#3+UNsskpatyU)7iN zvw3L9=2E1|Ypvf$>kkwUz@8vNnoTee2#%v!TfOD(xI`huY?b6*cF31vy)$EHmwU!s z1%@r6$VckbgkUjX*4h_J9C3m5z2>-Be7`D4dzU44je6zgi@=*MpzWEvLd=&h+b)Wx z`?@V>md=_3Jn|ABQEO~oQ^&HK>T5e}Es*6A$!K@$fOJ@*wDM&&STrf8`={^&T)zo& zIKT5{s#$8Q_LK^4)tjLQDi5GsS>YEOVL4sL0c^yq01&H{%VEZes;qha|}jVr>`$Nv4ltKb&|dRQzv!-)9zhHPdCUsdej9J?2vaE?qyfeu?`rM9o1C4 zL%S#1>t@B%0(gF@6ok#o_s-|1D^_vYco;4^(0rv{re7Y^^$6Is&E;KpfUpT#fa2tP zOgV7@e|CbjshD3h#-`|C?Gqr?*OI%OJT_5!pJ}G*5qg$vHUmp>7tK)x zaA!r`!=tKF3ZjU3r8sN464BWLF&EQ;7?6c#d%}QXfaGi&e%$4H$s5CDeudxYJDpZp zF%|#_`jE1$7#N-QVt?uX?(Y|nI21W~)l{oQp(G+__TnK#=}pwUN-2jdl_=(MY_)8K_#xA5ud z>v*R(NzwFO02dsB?H=ZaIjhVexJJ2BL2iDKwrl+(WMyWuX*m+`j@lI?@L(u1?owxG zmYxeq@6sI;`7sgLdWC^0H^=~T=Yc|*ps5LH)IQ7w*wdp-U=p9({R{oCL{1kxnGd@b z2T=vm6XwlA;LM~MtpnrLFI_}u8oeq1su?#h zYIxXajJTfus1@r(6;F!!-tkfPv42DhLJOjSH3H8ma#Y`09s{=(BBKmTh-IRF@l@=m zm1=oD0ZNv7_^l-XW;g|WfU@0Hwfw2D^zb@1Ko7h|4AyW$p)K}MRWq0Er}IddzorIR zkkY1FDonI`{SChJ+C|NS2l2I*k+fFy!+S~BkoDH>x23izn{wtb4(-xOqsd)QfZ=nT zW#-eyz}AS?EZR_#QF!;;CwpyKb2Vn0#;r$y!Or7t^fMk>2>bl;je=*n>o14_^5V zhJ8M7!v+AsDr*6#smE=+M%)%I9A{J^-Qx|=m#j;aSM)mTtU>bD%LJlZ%+66ynVulJ zARW9fn1AIPUX^l5Y)I{Gv?>m*^urugF>z!U)7fMvfON}cl*US-y6FO5iSk0sYA~|u zB1mO0mL7Jp>iu-s2#RnwF6tWYU#Q^ke+j6jpEH+z%-wr7F9mA=ESXigTITDRgl@1~ zDM=Qm&iG*Ie?S)bbr&Zd!*g24?&eUc%r6xP4#W{#{Gj?LF@jVd)+m6aaaVb#73l=K$k#@(f;|P1?fa(&X+Tf^+zrb{eoAJV!{+0M3$zn z^vpM27Oqx*R^sGi&V6{Tn;A&tL1$|?+bX|)vh;7yT=mWmJlviyfs;xye zf5cj~kcbr~P$mm&;P{E&fD|UwlSE~VQrE2#^6G&{hhLs9E}xCZhB%ZQ1CJol0Bk|> zb@g8AZ@P=20P_=jH5#YvO)Ch(b9)>i^txxwQ?BDgto9Y!ep{{%F3mHJd8V&k8UvX) zL_TEeGspc2g-p+^-kt6A^m6JX15jlIq$pvj#`~IX>MEHD<2c6C4PO*wEe_(!yF&^6 zC`pZM|14>!L>>r~l_7orv~U02KY7_uB{LAnMk4k>&J~hfY2cfBrDng$GH<%>5UFx2 zpluNQBz^3FxH#*<8nOh_+~3)|_Y*m*Q-NyJ1%KG45#2BbOpXA872Sjf4fwO_NPnM( z6{*z~YK;6CAF@jT7Rs-$3>co_D|R%0e-R_Lxm7v(W3Z`Jr(z0y{?&kgIcIwBeGHMb z7^z3s!^{WyME1(Se?F)0xb64s{Rmm+!82l%Hur;2SDtq)nTtn7?z@?NdVbH{;+h7quP@ON zdRMn%$o(zhNM=4`&T+Uotr@G?C>d0NH!#wRCgjyhb%&S$kmj49e*InZB3|pz0eHjW zt~iiL1_;5RRncKyz8K2J!0Vl>w^dQmg;FT0bjp%1Zm^1f9)LFd>%E5=2gE>% zVRbJrw4(DZ7(D|JOSkxEtk6JNc+X;)+iAjkGkxsSH-Olll-ZFIjP^^=wCYAH<(Dct~Z`n zPkvE7;3PpDiHe}C$q-sMy1Uh)j{uL}uU{c^Db~}{D(JgcJ-zBszgldi6-aThN+4Ky zeb27QlM|3K@Z;1cKy;zMUc*Udu-76DNo?tE`W7c&H%jVTo?5c9=Zq&seP`+vunyI4O6sK_ zG4sDp+H*GmT2U?QW;v=RXb{ra;v} zQZ$eXzn=WBlIo*G1Y5Qewl3e>GU;5b!unS%y#f0OP|Uod>vpfS$K~8F@xlbd&?e9h zMw;$~g43elgv|2QUCDN5{1lmsGu`cq(<*AyYzIMQKgX$X}1^If#>LV(*rF zxu?xbjdo&Qi5b4pAENDvssEy0QCFeD`!%4J1ffo!v_XB~P+t5^asgJL;Ou0Bt>>_! zq@uaj=*kFuPXi7#^WIfv)A{{F&gG%M`FASu(I4#bSMo0=!3}WmE60Mdy8uB_`1DOm zw5qS*iUq{R3&p9wa~OKrrvw!pBs*OTT!R3QqJnndr=ExQD~Ruq6XnNqkN)Va-WCOJ zeSaa?zoO!403^l9i&)UJS^~b81%wxFMlSnO4A`b5kD(QK7bA+P)NjCJxMU9QJ3 z5)|$&d>?s4-*K6_#h^~DN1G-iEnl#x>$6np#b`qp{IaLsaS=+Dv!Wa6k(`G!s~e5% zVgXhy{Ri>x29-f>(*D>gr5;gE{sdOiNP<#o{z{Nn9JY>Qt`lTb)b)Ty=<>ysh=669 zT0y>(0&^9F!EE7s`}<#$AM4a;8-?$cP`>QxaUtzz-+XGoK<<1i%|^X2M&3BIRpEm_ z`7R}R>18Jl*TMBF{_-op8AT9l0M3l0cKi=OXJ}hvO(>BwVVYR#|hz>K)}?nPX>P^&0J4EP;A${;|0hBF14E5@h?z)Bs@O;R47}OuBa|)=Lh;hx{eHz0VYrOGu2a)x9s65F&bJ@r5ScT3Dt#RS@c*Q0?}ZUK*|r}z|hA&J78 zjTWiLw@|_(sFWf+A|+z>4p}#E4*LLX9d`FtJ(F}l%e(Z|{O@ULkLvyGVkpYBa~-sT zBrX>Ce;0z}q6FtENYZH9qC!cXWY2L+K06{pA9b}uQb}J1S)snJy8R&*)GJYMFT#Ic zNviH|@5KcV{ObY$IDM4UEaB@^xhxUk+5*)b*!mdH`Q$ytY>cKtg5=U;NFLF=<--xG z>As;`yxw#Ye{Ilz&4KSP7=+>u04lDun(t;w+$2_eFpq8+yB9rG=1_r@3hkhN%gU&K zp8GKkF&ahu?Dd7$@PBgvki^IcW~jDNN!`&Jb;96BdD@3 zOnK8rmJ*^NJ`aRp!YwUiCIZy4#r#Ttfhk~j{&iu|06yx#`9rjjPHh{;NIQtaPJZO( zn3Cn4lgfPfB&!h8a{mb&Cm_skEX3&TmGwaFN*L95hl}m_{}*dA=q(=B`K&L|$g4EzGz8}PpZ9;@JnpgVHhWQd)6Jfs_Q6$h9Yme}# zQbY>1S!`V|O9h7}j%z5ZG-N1HOzKN(l<0#F-X(?4bzWrerlWY)I-D_5YFfg2w1rfZ=jk%~dUoNQelW zj(jO#wX138>=7!IU?QUSaAX4yRXsRle>fZpZU$oUS8@N} z5I~QfFno`1t*NTh(-u#vyk`UhsbMMd#y$}T{tT*-LXq($Qehil@`3_Z&3##u*Erjb z8o^*=HMN#uw3^ynG5O{H&!+$SupCaTjY6=+)c~S*et2)gNS0QOn+Q4SewI(2V|0ZG zdH=paDRgA~y6?l1Nsd7rh|I(_*MpM-a5DZQGZ!C{uz=ifCXv0>B2pl;cY`=sL!$Pj zsX*Hn0W8{|GU5WFr6J;iBGM3>#ip_!bnbD35V2EROcD&F#DKe_1DTsC*JMp9X7>Q{ zCI9u@OyIdE1xLkpL<&**;=++l=QKNPoDc#B|JmiIi5BBJsP=yDV|-W4?~Lk8RzGC9NW}=HYm3KBwmX z4h49=`SWFxF6L;rcMAf5(#2R#C=IF&2~TJioN_AvBI zfY&4Wv2@__kj2zecX*a~FQ+DXzIh6QP zBZJh~ytLU8VyFK*g-{^N)u4 z`h{6Ls}|aPqM1c3hC?uVPO`Qfqa$Fqt2~r%67dezsMTUy+B%>daAJ&P-8P6RT8)aL z2{?;|2l*n($*a9gM$6A7OOAMy=6|R`c2Q7DM8-Radkx2MP!THBq`aVSldR0PW4doD5tTiU`~+S$};{A9X`0lfGVl+vUj1)o@c+qlhml&LGghkAwh9Gq(F}x4P&F^h}FA3E(pXpac~T4Vcx zD;Wt&k{K9s=3j|}M_{i_^6k6MF9O4f{1SgEWF)@{ui>Kof zgXwoUXLr8zYtp;Cuf5K-UOV?NNLT*sI}Y5L{5sR$&TFYAJ&_?spd_D_^^GH1b6)+n!K8Zp zS8Up&#UE*f`5d+9J!yvz?^QYNt_JS;3c%y1HU)K0%7wxRuc~Gfe@-X-o@0Re{*jRi zj)g*dTQZ)FBnD(@6Cpcg7yO^jM;wM*RFRqREv8@n7t{f4JdHZcyJL`id_hxr;rn87 zP9({I?`p|O={b%ZI9g~;nKMqk^$M#PDV`Gyj|QzG;I!zQaLjeHKXfXuHXea#<;WLb zeA-@mWUCBEjVX9(ogC-dArWQGHG#i|aoCg%o1M7o9$br3xx4ta)~jtz&xCv{(3GK7gs?WQP*BN|hGpWsyBlZc zPj>}{OTNZ+<6*eox*k=p3-1WwN6~6rXjNPc_-6BEjazTQX{A&TG95e|LyO~j3voLV zd~i?-`L#wn3M$PAyrI1~Wx0{QKI)JVed)pPrL>Sf>9Ge?OdQ<7G?Fj3V2`0AG^U{p ziJ3(bJoIHUAo<;;*?e1GkO6pbuhH99YJ>s`&BjM$iL+l9@;Dt?%@%0-%A#aIS*&gX zw+e8rXtfR8X}HE(d%h{PJyg6f@KwmRtxlRXU~sf#kwr}xa^3)*C~(|xi~}o}5Mvt( zorbz+=73#ehy`DX%!Qpv?u-+gng_<)wk8clBBwQc7NE86Gh?5ylT<~Tjk zw9vDd6gvOV-g`HO8q6I(DA$+M-Yz9^gz9eG3)o*|t~`X?HzW+%UMf|0h!Rj6nVXaq zn&F+g3=eGA08mtVnl&-;TlBQw2o?Em8HcDbk2!riGsdd^PTtOG<&dS9jv_w2A@}R1 z{w$$34qj}kxw|~)f0El2l)h)K%>)gBr|{Mkz0_&Q{%sUJ*18i$hjA$@z{|YP1R5W& zIceotHAgu>lNBWj^`G2o{!RA$vpWl&S}Ws__tfLDf+T0dDU9i81Wtgy$F`y+Ay z+~A&;w>?;bE{DLFz=XNFoHG4rTrsch{FfnRH~Q>cY9HSv6@2o$^c;Fl(p?7)Klzh+ zmu{8M|A_Qpk^zmEa$hq~lQsvFwiAy6&K|gsj+?T8K6x1JWEqQx^{v~(AT+KzxPD?n z_c=-j$8H2m3r=od&n=U+GvY&O-~uCBXMGH-v+??Cqsj079YbW1fo#axKmXxoVxlP! zn)P_6ETOBk*74!v5#r^yjh5B)5AAFz;DHY#LA)XatJ~d+*XKfy_0tdEUOl^CO%LVm zI#vG`)HmjVGhoh;6yh>Vc#bNdP5fMUz2nsJv}tLfm2TlonrecTdt|g{o%@+Bw^n{6 zi(1ny1r*8pnPt*#~Jb+M+$8W(gCS9yNd7;paWtuh^eCHt56;| zB=JIFpQ^6@47gJ^kz#81^@@I8cNR1BsW|Q1j*>YZ`$=NHtAQFg0C9++EG8>rC|;QN z$Yk?zWDThIF5Rjh`EKjz>f2s#{hD;w^HDcw@^Ce@DUJ4;Ih&n0t`UxWt(kz=&n9i^ z9+e8NSJ-+Dq1OZu)xI&h><<~GC|0bO&}teODP-CF=0kcs8!HyNyR=vix%j&U-fct& z0*#M=Z9o~I^Te3t<24bXUmdPPC`hU9hMa8Iu#VQc2w*uUVFbjE~(;?^EeGkTM!?y@*hiK>L#gi}$zl_oAKK*Ihy{Niiuil{0uZofwSfSUU*L22{WZE#mgjiu zQ^n>e5!C)VCfQ}Oxmv|6G;Be^Q^x=%uLBXbiGiq?v*>vhHpbC92EmxOd5S_wcqSPG z3tb*qovo~VeV8r*-2r9CaZ4&ovtQTC`!uO<<^^XK9^$_I8|JDkazHv(uq{i5lIn(4 zXSSaTI^~y?eHDXO>kWQFfdbB0cTatxgtV0#w8t@eqi#el{2SXQeNMmW9?ISIwD>~O z&GQj!E2zBQ$Hjn5^nQ7jp<3FBC6|#%r$8MU{}fj_EL!b2g#z)y%>0HzviK8G*S?U? z&zdyx9w&&4^WR_VO`4sb9r;;>VomUK=<_R6x+KhL1RdK*^B`t9GiXO`#eoGQauo3| zg*GaW)xJGPvUj>bkz@uTrnOh@3&7H{L-ao@#E)|^=v5Wcu#3utsDkctsK(Z_YPvFLVoE|0|&bKM|C%7 zTrtN<>`n!5l3h%M)xu?3 zQ3EnEuZh`DdnDSd{e8|Zi=GeTs4`&K(&lKtTpdNb2TTmSJVQ4;AarHx4uzNwqFyU+4NyB1tQ>`R{MB)zMoYvHwFOp- zlz_&@g8e6xUZ;QuF$kkjSDd3O5+?s8K@Kycp$WeAXWoYYO6Xo6o%K{Hl~==aYXRZ( z6&Xm4U%Z(cQ@3UE(;rP0R^Sn4f&hbnv%SsqKlz=ASBPzsIWN69xGk zr-zZ%+W{W7uTL#dAGS1}>H3pHrJ?urQCwzsrZw%I_1QRlJ8$T%++Z7OVg|FCmG=M6 zjQo#PvjEX+fNZ5pw(|n5EYd3Pl=C^0WhQ&~J9LZ+{{v;U-Z`&$e-0M0BKSv%+-S8O!Z<#b;shBIIA7)mMPi zHxw$~$ZGdv(6@i&#xveGxpDmET~$V@l^|!*y-!k3t0eeu2@^I;zr%l(qJ&8jk5+sY z|5FQ)@kSi#|1AZ~4YmICUA5)eRqEr-1HrSv$KH;I;*w3@j4fNwqC*X;hM1x5{VCwy zkS#p;vc>+b>wo51^gP_-Z85cY~uRZP2Hd@5F??2u5tVZ>h6$VII0r-kH?&cmIK9c_?zRi~RAS9bw)s z?K1e|w&>_S@$mgX6Rlr2HtW`A!=8NDc%oj+0P9t394Ft+a7bFloHr2IJwklb(2yMxgQsDCMol!o zJ))*_=p__qqHrOW(TYa!F~SvlzA_XJFby*95x-{(H|L<6cWrg-jAs|qM^)lTpr5Q1 zw+2sI$9xwkoGmmgG;CE4%QE=i#>6L}X9lwdvode`E#N4OAK1crbhdqdnwQ zi(K>m(%H84-8LLUzB*}MmSXQWq=ADAKA1DvT_Oa&R9Lw3W#31E&)Z6# z9Q;e@l(=o|BQqcRB;Lr{-pdoaWDI+z0TQA@nVgV~io%&W?@aZF%9w;hdCNB-FQ~o^ z*%8drx>T{HGiPLrfk6E#AIzA1qMG8fCF-jCH?csQwUxlLjNBj5^wHN-z!_NU{oOh; zY3Q0}$De&Nm73L~8cQz);(adZB9#XqgFU}#pR{5#pkJnV>iFfC3VR$RW&Q~6l9E;%fL)Ws(TA2_HIe$U5D^Q`JnWF-44M@#uOQO9UPYa1!6Qih@jj|! zmo(17Osk=(S_ZU1Zi3Qhrg~+&BjB=Nc*7WuDc21o5|psJkGe{I`0GXWosm+x_ZyFt zxhu_tZnGG3w8iRDAKNehov}Mo&Ywk>sj262?C*n;Z;WeQ2W>yr-ZsjPxHQ{pxq%hZ zh}C?7oNa+mPnn) z?G8T_wwBpG7!jk!mnorhMy>}Vd})u?9~kZ2c@k;;p0dOV1q%IKieTOTJhW|W7WJ{O z7+cujCoBJ~=7Bviu~wLJZW3SesH_sW;I1mt1gnC5qEq*1e8`Eq|6>ri0<7_VvuU5(3Fy*_~A1P+S;BW0!@O{51mnv-M7~bxc zPbVf?MqRQpZsreb*44XK>!WlEN+gh& z0f><-9(bWIDQ54E*7lqTmst?Dck4t;nOCed;*Qk*nhHPMC zcDkBmbDf^_@>mHt*b8wI`q}_In^bNepmd(3Y{MUj!ojL@@*hf-*o++DGl6{h#oi)L!65R= z!+VB&{8|jZgA^at{+#97H+^i%HI|^)NzL0i20h(9REw)7Ii6>$`}8JkFoPbd;vSQ< zl#+dPNlB(|^OCcvW?=U>s^f}P#VyBn#pWwVFOWZ-k}nZxde6fiyU?ISc^>2! z$w;MA^Gl!ePvIRW7GgC$wx4It+!jY3!Z5Izh6c_gq=KxR)~8<~0pFrnGS6*i=OBe- znhFHpHIw?zMo9&}$wmVg+KO+kmgETT-wfLEuu%*oZ=r{*g&lU*=-R(n`6P8%$P}>? zjq)e+qD$|T-kAyXY&BzJ2O2b%N1rNO9DDJP3s1kyqJw)w%N|*%BBMJU)aD{qGH|3$ zOoiJm&WQeX9zzMnTA=#bq?;pRG*SI$`%QkxTG_D_aAzcebL0(@X#uWgK?R}&e`k|m z*JfJv*zyC5^&G80BdsqNtTOtR$uiRH+0wXKn zKs?8T*K5cT#aT-RI?M72w@2H zaKApB-MD-!MpzZhdj)S3(o|R}-{}%&{mNdss5HO?i9tXv72@}w-S zylt5x>b62&Puu2P(p6 zwUF$G=XgQy&$jm45WLZlUp%$UZ@yK4mcw|KH%6ZGgV??yIo}+Hi11J?$Ada;ED~>=<++fLs8YU*6rCM}3YDNECV*!{dg?2o?RtdL1vCT9u z$uNK-&MR`-q1m;NEPpP@p*1LfU7;d#>s42Gu>W1q=rBx@dAYN&o67o9tCW6DOq~A1 z>mXBo{4zfy#QT=W-%6Z+h;1$pumk(Hrgck{D4c968H3Y+O9D)Sq40x9+lR%*iua ztoj2+!u;?2saq(Yl@ciOw4m9ChS7%qB{e|13mq`}Y4f!ib5VR8Oe|$-%bFUbH2fz0 z9r7Vr8znOHJvR7dR(y6+7Q0*fU2rSN&oq+>^C3UQMqg|j_CR+_nCd`1DH0hCP3N{5P9(Sx5r_IWL!g-ZOBgO4Wroq*2Y!{pa6nZ|D?Car$s|5 z2fh7{?tH`sV~>QSt*pZ0E2*$o7sr_?pD;eDF-AYfe!NG;{C=1qXAs2VSdYRMSnnDO zqbAv1^RWm7IN`jp|Se(-rqVYgJJ{oe;69_hL?(F-Y zf*Z0lK$MBNBRbXrUrxq6tG72P=^X)NBs&5Ye1`ox205B|<(qU@pr4Iv?@KGuwZWpT zK=)dC;ZTBN%`2asOWXUcM!vRus50JV{uhL~X*>zK(aKbGEd3rahSA>lyQ~;4)|^c| z+cqL;r}BS{8~-Ih{A4MBKH6^-%1$3&t=tK-YQ0*rz^@e$oK7}~C-}N1nh86Belslv)TIZ*!SrQ+>&O1Nv@HhU z>@%k;&Bs^Gc|!fxuvuRfqpj|`;sTG_?Ra^8gG$hc;?UPgkqrk+5Juf6+Jy|UMqGif z7-~z?)tXL}UK%v{>q$vc1N|jT5VL%2UIFBSQdP<8y#J6|7#gVq9B#!&8%jC?exggM zNQNnuO`>Z^xWqdzI8Ma2?`Yjk%EuYl0@_X)l2=ey*SJarhL)XaKPR86hM8ifj4}l< zb%wBj+;BWB*5Ex56*aPQ;0stf6Pi}r5CF%#2md`|_#b0BQC}DXTh5%q+$MIU!CS0& zjR^ZQ9LR5%pCxJ$@c!S##a<%mnG{p2P64;gH*Jp3Rv2k{GM7_ ziMpaPh@LTukp<-j;7GXAq(!81D10E*qGQZp6(Zyd9FC1cx7PJe^EUGnjlA+Yr$t`t ziT~k_mvI-SldG6`1ni%rFetexzjwamexl&O@3`j`7g(y5hef> zZrmYPX=dk5u>cN@+>0UmzWES}P>rb~MZ;!umXZaTtL-@`3MpcjU7ks>3nTd%nNBcj ztmg<=ml&+$=}mX)`L=R3r?(=_mH(qU121gZ;=?Z3KGevY{vU9)j~ZC zWwq!jC-)NW1mMxwwfB$qGKM4u5V2rI;haAK!i>3LAZhA?@`Qt6l{)~J*=hLNvR20s zl>Qwj{l~Gnj{;asW9(=ebCtdBkwJM z;@Y}~O(0l+01X5uKqt7nd!xaEySo#dMuJXlDZH?(=C5Z8Z9>dq|e_5JF}w0V-ADbM9=m=ZQ&j4F+=*Gy+5Yu#S9&}5L#+-3yZf{~8b zRw$@J{k`s4^eFYPu$uL_b%{Oyyum0)ois>{Vgi=v#`{eG1|;ww#9IbJ(q<(w=AcmI zv_qQI5M&IOsuVtbQ%*;D+l1tMPwUfOqtW}G)(8o)=CeM>_c`38HO)TuKSK4|(mz0^ zT|KZF5|c&;U*{{dcu(R8jtVCHM9SHOvl?%t^eH`bGE;PTxkTAljx}oO8V=$g$M!vU zqeZ7AnY5TyNBf7BBmhxpqmgBWEy{4h!j!0;O9Y!-DtX?l!amkiQCPHNmYz}i-{l#Q zVdKqnE&Y1(NF|nb^U*(92IMb@RMe$s%HG0oi!B?H2mWkDE&5wb(}dqefnmR)^koW;UsiZ)C%j@M8XmPH(^uQAw`QRJu8KT=q4Vx_0PvD z{P$QAFgdM9*3NUUf&J*jicqD=p?fge0norqA4PABnkHcpZ$r8MVUnxw{z_ry*htRO z{vXZ+QI(~hp#6YexKD2)Zhq%{{BeW{ii9AB#kWNEq`3i3T$~JJV^w?bv&0M_KexZs z!!k3oC#;Kh!oHWj1wqYJkbI&vQ-ULfT2GS-IRg}}3*n_afAZXP(LN1D*p|`7qy(Pw z$o@n9w83r`3Q?sCHQY=vQ?xOTZ?o$Mn% z?p7Nc-Br`EM}k(3RTH-GO8^OR_P%D;0n8P?k38$B3*xf;i(QATSQ4PpU4qF^qB1I9 zbw!Qg&uML2B^_;a)SpW6+${HA7qJXCLjkOnm`CbAy$TatPHjCBpNck2&mY5CWb@34OC(*EPVY%^7ow!xu66UBB?}$1CadJyBSFpR!C=m>peLrsL+9 zXXx%ZjoK03>s+kfCib_==VNsZ?li<{m%rS%ilvIN=FE|gvBWcJS&1ri_Z5385}D`@>^-vHeD?){1# zN#Tw!!H6y^H=OUDU%aqdo6okf3FEp?6l5-al9a~ViYy=nbcEi%K?#=m$Ma>z4pS0b zHq33g_I?9GiQsGUPfPb0){bgTMH}W$fF0{OCx*ugdNY#XaU}gOrdovuP_F{w*V_fZ z?flLo{QKCCBh~?h6$t&SZV{j8fzsex$)-Cr5DJ^-fJPx@icUBW}~ z{R%-tYdDbd@MZvfRxI=EcR>CwFd3yhB>Joov-X$S+0A3n>4qoyM){ZR9sAF1?)_9kcGZI2bON~i0#MDTPCirE8Bm>f^fk)@{PAV- zW%+HLOKJG~v=Bc>J6kvTsUU~^SGOZq&`n0fi&v)YYrIF5)UEzHfc8nM&M&Vki2$Ux z=s0pqcqf<9!SW7cZChf1jQ+&Vfqx8@7G%&Ft!B|_bz6Xdn3D@H8~{k8hsE0-RmWp5 z2by@yJp}RL@n;avS7rLFTj^&n{3v*R=1=4fWRm9LEjGUL-=EamTSa=06!no z9h;!n$%8#r3wefN%{9NwHd3SZE7ECbX@N#{+#p(Ns3s51PHHmd)P@uv+zmCi*$qGn z!rCJ`VPv=C;G7K)pLfTI5n4(;idtG@8khCZLx3sQA9D>USo<-ZO4MJ~q&f14Lb_-u zzt3;Vm6sr4tm^X2&FWnlb6ya1l_X%jnn(!m8G4$3;+SBr|klN zKw?$<>7BcMG8UCTT-TDJED9~v;>hAGYCYb*2;)12tARR%oxyIKk`-@_6@`Z*H93$F zsF?oJG}AWCefmUWJ;HL+w$Dka^Xb>(@F_Drb4?CBZH8Dd(>J&EVdEYQ(&FOkJLd8~ zUy4Y4R44=iLIN4JhWK?sh6}}Qe2Y8$P~s*EtY(>yy%(^JQ9L4tud5pL^~Xq@x(}R3 zl;sxQhAo^P-ZqzaIc~Y@-% zQ1jp)+!2Sk-sZ|^H^v_gh}4E6Dcut49th0f;V%aZ1-x*k5N88|pxC1`}+G#<9RiT&5 zkfg!`PWXB{03qh}sS)K8WA}#~-=dIUbf>Fec!f#ffO2X(zk4oUA9reghBP?eJ;PTT zK#;iF=^RGn!Wo@dq2qTZeF{-Q`z5@?)@FOkug1dJvY5obLK4vv8T2(ZJu?hn{;tU& zpDjevOAnrY(zxGd1eK3zyf@U?u)SbM)1D3@=7P8Y-- zP7^(bwGxlM`E%|iZJ{xV-{zEJ}~Fb<*D zD`C}gtinPnng{2~#yNiwjGVA(p9*k@`O@k-sedDqIMQ zp<_=!MEz55{M9)5`=!bKn{o3$di(wBsKM{5OtW7qc>YbJJj3tQ5Yf1+e_9g1jfpUb zRE-+E>OcPbJHX}E7}I~o0{q9{1)Cu+JNY>3)Bi}~_)B{JufAbf4X~Sn-xY9v);_A16 zmM@AnJik}-#%8+*SVAXd?WAET&G5Dzxt?MQo1n1@U4{(~9^ya(him)fzS@nvg+0le zbL#{<*YoUw#>9SZ!bXAT2VMR*2-cbSyf+yRWh0xdTUJk`b(_o0<@L0D#1q4F$N~?I z4Xh_(Y$symhB`HA;^v%n@#~-50Nxb&@MV3YBqTFZahOD)BTm zxkTJc)$E1g>MWF&*0m3@N~&=Z1~0?)PPg|u{^BYx7FYF=jitxD7uhP-KXv(@%4BCg z^SyPlK9k;0E%PeE&o*gp70K{4Ct9HIHY%0bcjmX6ZpIXa<=Po%@BZY|&Sy3k#K?)z zUNthv7lw>;y*7l+n|Tzol=LEh{Kmj8-Klg;m++x5QPU`d@H~^?5HZ=eL&iYUPs~EB z{^6veav|wDQB?HW3FVlY8u+!IV+KuhIsDVNp(MWCE*!>oSJT0Y&R2@MZ0lc|@to6w z$)0(Y+W5mvr{N3dDVP$Q6gfWh9Uk0b{``quga%K4`Hok#kw2mb*#lvJdD*M69V$XH zz!S^iRB?p8P)`SKIre3`kd*Siaf7(7sG90%)WUoFoz~Ha$27d8WfC}F?tY=!WxQ$- zgMA#d%X?r0@KQI9hV^L*Iznnq`Zj=ZBbC~mSjTwtJlOE@(-?1#$)|FvES(9RQu1^5uR*++|7 z)U%3z<*y>UZS5DOIQG)9muR0i z=xkj;tr`dLJ#`uvJA|MYX%=V8G9lO6P9~9;U`cxU4K*;kn#Y>|=49K5Rte34+f-L2N=A_GGbX>eMa9`vIbEGc|7$!5#Ce|BAcqa zl24Fe>v=i#{X-M^5|g?1M;7%F477^*sA_(bDTe#+%HpYh#f@GRPyAap<96JlJY zdeLl()b4HHKABJ7zD@jSZro*B9}w+dJ9hRchRxoR5Y`?HQ%2dzX-rj4g3yinG@3m6(l`sO>Ay$a(@+PU2IuBCG*6Pm253LFpN@{m4$p4%x%`~}#S?pk70|wfVm!6=z+Xs=c9FHG?oI8sZnRwh zKp><~+&dDla1eS6CplcgIW1?uo> zh^S@z2IhZS@#xKS-53;OYS+Gx#Er2qj-N&o=XKfsL8H={cCESnlWJw7ZucAc{4712 zd53C0ePOSy=k|$g%aYbSenMU#??IGjWHz_9{!EdJ8|p&~cjbI+-f|K5pl4-2nP;lA z_IIe&P>kaf?dgOqet7~8Jav7A#hiIw(V_L72bqz5)3p)I8U6#(ZxO_Amqh9xAUi^9 zg8}U{z1`eL^`+IN(u;*Sa|_wWc;j9iCF4QrFJ7IVPzm>ftQBaZ8%!Kj%c|4$12=A4 zPra!@>MI$yJE7rsDb-$JKbFKNF!2+S(+y$Q&FUQAek2AK;By$FMI~0sI=4;SI*&DDJRCrpwAuPQ< zA5GU?_0R_2F1c&2-r5;y;GIe}y=wibtqfKz)~;4j<|E$W@_Lr;JzH*co+`B{)i}^C z+-GX9oAc5m+_EC!X702yrXYpiRJjTA$;S&=lYJ1g6Jwr;ezyV37d`5m=1t z8*I->{_q+Gcebj75Jd-XlXs6oKL?v8g|W78lY=R67jIyeDl0?b?aC#-SyY* zJulIMRCoPmh))6nh>u(}dogCJUfK2leQW1J^eRjrk-5A59#+BZPFuhZ*C`2S43S*2 zb(?2!%)I`sM*Hfzr&nPL0NVgSYVM){^u>{Ng%7j*YAtRNyWGkU`xe8fEts9<%!<{9 zW#e11-u%z$cdqIzGp(}T_z@}HZWI*PaqzWCGH?8>E>_qy;i^c|X0$5{pWTnO;AQOM z`e%GsH2^*%T{)(b5)NuqX^_f2x&U1#Uw>WK6nFWk;9{Sm~A8)7i6A*gLxjgLd!kY;zb3dy{z9s&)^j z15|hAbXU^lJbZS=HC@Ka4>TQb>v&?$CXuUJUYy(ww~#@lwqtxZPjUR5btbh;i&R<; ztx(^IkOxsa%>xccdxQb)p}(GS)G!tVKM$J+e(bAd;sKDFTDpzC3tlwfVFd{FHpw%i zbrd@pYElIwVy0N=aLu$f3$K`H3VOW64fANUC02-z@4F7?-n-{8zsau85Cg3mHi8xt zvL7dFm4HoV!p)AoM#m3B_OUHD=T@+~QtJ=KFnS@LOgEDe8D(15&(h(q%)iFo0~_T) zlwYAM)(z`}M~BAdy6Uc1H8BAdK^H&060u#jm?b}7)X@e)>?jlv>>2t#Cx78h`xW-k z25K-Ok+D7_55l?Fx+(F7>`r$F_xrwIn2L!AqIs8!H7) zXzgnVSzPTt29{A*FW~u_x^QE*tLA)&8sf6%+V}?9s`AT}F))T3Oon=qPwP~H->U84 z%X&>m6ZAbxiGtSTPT)(j&5qlj}0i8gs+v8QxC+bc-Wbdb6!-u4hPg!ER;qWh}X3!Q>6e zAhZF`53^`T#2jajh7s;w6X0eiiR6dRdy{*9U&sLMM|X?KT)Q7{Z4~jyOp?1#S?b0) z%A^YJG@SC2cJ9+R`=$LEzYI@BrbUhdxwE%pv`$lzvDuRIheYYvRl&KAI*CTIY$NVt z8RUMy1mbyCGIOj81ivlNJN-b0@$D|Sbhn^Q@TS3#fz@R?A8M*wSb(FW9GWv%)$H0C zv>&oH?OpITf6%&gXjB%iilHhR@m`aYjW+?#9(ErM$|dK)VfCSf;_WaK{#G*ouK`Y}@N2eT1n@g*@ z_!0b>V?nNqRwZo~c1{~Vwbu_a*~Dq` zC&3JR>)bxoVqKWPq>whWr9MJm+_~6K^KQ38;S;~aS3VDie1~~&%~rp_V1}w`gc@5| z_;Qy!JbbUG*s!fmE(&Yoa5z*i60z<_hDYoLrI!#gn)sP`lv&f{O_JrsZB*-6>b4fo zBWd~Z7iq=)QQgCoPukf>D3F?!(VIKW{JLkqkb<0hfiN#r8~H@vZ+mv?hpf3Psi@07!LZ-$hMkH-Vndq>`4)ij&WRoU=!q&fdt{}G zh@Xe(;y^9022L+#7P-XcSn@)tTUj_rF*7zwsN{JjxLgnDfum*kIs!r{ydMxi$We-g z&^#$2>DA&|?PhwVTy{CKX2X4p-@Yxh36a|OX*G20Hbj@Bj=)y9#x-jT~hL)6AK%FN7 zszx^=E392~Fyh3vE-?!IDC|s|*Zd=PW>d}%d@EPpm6~h{2h4N5gB6v5hz{lR8D_nnd8^COMYgi2Uv(#9vv9fVW9jI1WPgvd0!gR}n@-cpY2H3b zPQDz%92sYLfDbIP9bZ`S^dElyTKznUe0lvIVYi>AXYYXF7=u|mS#>u&>KA>4q?1T*T73`>C+eNK0373L6l zNPsc$OJn^v1)L<2C5h~hY-0ioZ(kzT+6xogygE=J9_mm;#gcBVP!aMDEyfM9Kd)38 za-|8&5h{Zl#jki1PF|k7JVo3+7!CSsh!UV4V2SqA2oz3^sJV%KukT;j+OK~utcOot~uGOuV3L;j|Y4!s9))}`$7h(fqiczf&N2`%PsYp$xc@V%2oFhKGe~0kvmXWYk zNOiA!|0jV!M@@K1U%OLI$$-)AsLG#W>BIi}!EW0p(LPq3wI-3ZIRUGH>~=7v-($AJ z2fh%dxXGu5pqCzFmbc!edNC!S!eg!sqr-OS6E&vC(>nqK`qEtuxBB>_eRVfXd9YX< zg&3{p&l?@kS{g;ub{?K{YLvqeQ{e`R>|U(qokzAVr!kgG$5x1Dn)@zrco|wn$pGo!8`@_D$8 zGbHCJXZV)>!{wP$oA^-pb)41xX6pkB^}pT(M%N0R4+3@!Mg1?UB|y8r=AmdpgHxo4 zp?$VDyUfC~Yd#KG0FOYp69Jla6a>R-na@wOVW3|sk;nDvOWN#GcJShy&tit(LJ;p7 z=#%F}z(Rjsn#OBLT?=S{Pa1R-2Jj~fKjXz2H=@H7>N}mIzhl#LKt^l!L zN+NoAd`8KD$?fPrG~8d(a=+@rImGyY8OXu0_c-NudAIxM%W2)bt0($A5tO{(=&|)Q z=Hsz4}h&nmD?m|C@8h{r{&! zXB1vI16KonH(3Hb6N%tRh2#-i=CYL2TRwH{bdZ)N_UQa$g(?|0#Trc!(JlAwhk}_ zf=wVoHCXZVD%6|^6>~Q>kxbC;C@5)~|7QWk??qaT9ww=oraGC*;#s}#*{zXciC|u= zn4j!u&1uUip9q@G*GEO*;`lTbqjBVf>4?g$q$Z{K#?q6WK|inh*%cKs-As__-o0NF za=-70cJzomilHeb%qwlJ!zJ?9pyl-W&s9vDt#s_Jx@S)psnFwOrB!%E$I0(_9uBe+ z-3h6#@4A|byYr!aeL44TmWg~nGuo%mHVj>1!Z2h8xvjcLiw;|x%*|3q$qXD<$D_j^@rF3nB~ zQz$;F-!<5`w<9A)BfTfHF0Bu$Ep$W)aPSyS5%x{yAe&ZGj$tYI~s1y zqHc8zykhn|@AXOS3Tm8k@eLjg=jvmMfP;T$79fOME-2qHO|RwOu1V?Ic~w=uWK!-o zi%|85ceCc7W>VgZ0q0ikM^s*bWmDmFM6-wcwl=LY>;NC`fh!~3@&Kv%6AiG)3QGnn zC*oU7F-hzv+cuO9wtEtEGzD3ufmZuGb!+e|aIy4IJ9Qt@w_KLk+TEzlxe+_0YEgn| zZ<)(xqgC^LC!A*0vp0iHEe_yz)N4sYf+5_dopYPFgQo3`o+Y)~@;oQmJhGKCj|JaJ zII5Z7!TXK#K$fiso}%~<5oR5MAB5v)m+-yjo4JHYrX0weEbo*=bweIZ`wc+HPh^I{ zHz!5&3my57iR!a&I2icdqUc$?DT!b8^5>aH?!Py}=L@mb9=6AK=|{#$DLvby9+Cse zcf&)Ja-Iliw`>!k@-<1T(2JESB-fP>H4Rjpe3KSruVX}T3mMK9_XiY zM-pJ$!ouz7(^6k8w!-hqhW##SO8Hty%15V*r?Vl@v)(ft^~hS&IPAWfg^<+8uh+K(!N`xHfcVTe>gL$yPX-qYshdb$-bl-8#C_TLrLQX$zBqOb+a zEgS}iS%2@<0iR#KZ#fy6vHmL`BtSA08vQ;i9Y8G29A1I|?=Ohgwt4=xK zy~^3TH^~OQUiPA!-7${+TCnI5og8Eddp~_ATI|BgU^g~>`vo#1BO)s48mWyLCMeYv zC3q7#S0$~s_s;JL1Q3}7jVz_#e@{fHc5>=~jb(T@eUZG4dBilpYRMF%JOFN}miO$~ zYq7D&5dt9+eUT157UUtk^l0lVM0fMa$YgNSTZ>^zCK578vhUMuAkmWo!k|$;T6j^1 zk>Yt!{?x4~0Zqm`-IaiT+9ZB^^I&0g*f^EjX<_N7sDtTP&Hafi;yWGzgycas>=4xa z%YTO z=0pzh2b4nRx;3M_7w@J67H`mROsC>VYx+57hqZXDUOdX`<0DFsGiXY+NZdQExbST& z@fX;3dimF{+3S5cCOKvzZd10Im^2xT=p$60iBPQ3TcH)9*@weeb}}PtY)ayHh=%ZI z56Ea9o3a%i=;6GQR^_8ZqAHWgpz9{RhIBq2A(tk;po(58jhLCPW#3C3tZC?zsp=fE zaPpKL9dhcPj#6)x%tT~hOc&VWSEv@om3vCIR_OLRIE*f+?w!833EQJ2P)5X>x?@m0 z=nyUYgmT)eQ~PG}dSk6{<0zbsKeHOFz;5OBe6YP$EHhury=}4+TIg5Wv5m>4$nhkL zRC|7{7R1F?b!PamG>vLL*ykYSMI+t0y-l9t(h|>aJ3oJVqjHC^FV31qKmv69f&8}o z^gYqjjZnj5D3eY>BqzI87L@N16+@z+2l~>R`QhMauw#iIRo1jye<)&xp?aMt32OpB zc$GH=@!BUy)?l612OB_o7@e(;1M(j|Ls{tUFWxB@{Lff`a`@;>7N~}2g*Uf#s+Q!z zDil8_g%SdS(QDK+_b8o6q6zV5EqTcRknOKD)C*4LWZ!v>OV6goNy7>RHgmSs}iS1Ucx4&G##Y?@|{<=ooP)j&pQF9y5oueo*H+6Q^G#tUf zGqnha&~UYV{DJvCC4HWYx4AAUnMPrlN*Aw4jq32egC~*nu>2R-WXd5I*^HKox@jK! zE~ebMwfJi$iNT8K`Swl+_U$?~CY>X+apiy$PP*;f8pX33$&UC{qp88G{U*ij!RlZ{ zDgv5jIuG$4RC>puSNU|*p;~4mwB)#tzBC=4uA^o{1l7crUlq;wt4^o~a z3cJa%Un9Q*H0LSEj74vZtaZyIHisjnLaCpmJo;7SbU_kG6UC-uBUy_( z`TxZjyMVrIn@@4DFZQj%m-%uoa$cv2Lf#~ZKcanp^4Kw;ziMh{{RhcsTKo4T-&}sQ z$H_RtCWHIOeXr|WhDKepxk{vJ_d|>2*JiMJ$%zzU)Q|Vy-JQp3Sh8W^`tVc&qTsK| zzUKVuY|2WII-z4)eALACBp3n;QyO%yfLaYkt87)0^c+wtEa( z*m4ZCTCI`KYo|z_r}rDpWv5wBHpuGhNzSWBYM@hg9)R@qS8t&nSA09hTxsq4U`8Fb z??||8V)tE{im&Fk)xxUjpa)A{0SUTyth#6o(VKIc)U+gtmnC|AMiIEOoW-G{2_fxxnf`v2F`uO8l z3~pxZL17stP7A| zUU)16dUK2D&Ek#b{pxDcQny{6-Ov+^gflH84|?9)3Tue~U`MOjUrc{k1#9rI5)<&b zCJy09h!I#Lf(h{Fo0r-as;jo4^*{u6fbx=5hp%i)M{DP>(N~&tE@W+T7UH8TsQ$wG zxmGr2g$@p{Y8~oZ6@O`9ejQ?oj$vq4ER4LwlcN1RlEchdCD+VL&>oNF(W-sgCkoln z`kjRmPhdw)%&@~6F8ho2`0~O>5H16d{OP*rR*fFm34=-OcGO-=4m3jBQGQ~kJMK6B zzPW9$jxyFFs;y^%4(Jrg-?%MWMwroLjf5Z#$C!ZjL&9EykTlP)JShao2VXy+$w?c( z!%7(+foIa>Lqd=aA!$QMc$V&O??+Lz5zMyoo2>So+#kDpqpxgH%K|&H&4qZR`er*J zx+W##L)2D|MKt)>y%{=M)E~n%qmv6YWw1!dN^hlK5)-xRvnPQPK*&n5TPLTb{!s4L z_($lb9Bv;_*M_Y7>q)EH9C^bdr)P>z`qi)8tCI=I=unIO`+&C%LMIb5;i2AZC8$GS zig$VRW+U1cP?)|yt2gS?V;6RAPn0Y&My|(IlcaMLR{R(df{#vjFkulIuKjc^dh#BM z{H(}@Wl_F^fH#G&{($|s8wFo6DrF3B@wD~`)N&?0in*1B96aSe8;u7py! zmWOOx(XJbU8>LY|pW8I-%JLG|FB{z*`ysQciVg`&{8_i&JNGJF zh2zNmbt^4LyXsdD7KY<%XCY)M2j}@I&>5G*)&Bw39}|^z=(7FatpDc|gNF35@f8^} zpyAMe5&GV~*)xuE7({a4dwn9%;zeAMmpLbi>K#aY?uHgOZAITqXM9LL@%Zl8#_in=6(zTn~Ig8y#3LZID+p%J1bvgN;V!vd23))+=n>wl$bHiQ?Zq6%QR(OI7abp6mV97wNWPJ1PtgAx@{1ZWapnCtoAG%Hd6Y^GeYi`HK5d%cMo6Tg~;1 zakjFh&5P8b{{FuZzoJtq-s_AnnS6QHrlTM@9aZ`W>3Qtp=bRbK9(nWn0ViEaco6b8 zj~dgD=C1ItCPGj^7?8jE*TOBDO42XMAW#DP>b;(w4|yH@^~829`W78ukUe*4i|EIL zdJrQYoW3ylps6&HD>hGTq_M=681V}fA}toQzNNh--+^bvG{+hjx)}u&08-P(gWA-I zTam|`w|?O@Xiv?F;=TC406!{&v}oZ%q;lPm;W_p8$Am9Ww1w4%#thBDQS-y{H!|c8 zHdSO9*Hte>1c0{;@A9Ii=lnudk9vy>Vw7~hEtCtmz%uI~!3=y9A_}$ z@c#s)uY3zo;a5?NUk7`8ZlF1CK5um0Y>53yT>s@x@acOkEdsjj8u1oNo4YwHVJ4n? zehR9!SpwdlMiS@nkOHNbMJS>F4S2%#hd?Q~Jw&Bf|E@6w(kOaDI{BBctLV|N>QdqFJ*Y2b+Ik)m;; zzq|pL??*-%9;AXG-hl24w28U2a_kgtaU40qbQ+w9F-!(}1h@N7>k?(Va7Tcfw3YfLj?Iwh?qppS6Yqbcf7rD{muMT)6UpKylp$JDBkLs+`B ztvJp0*);UK!5iCMkHm{z3?Pj8;+*7m)% zY?N>?c~_i5h9~-39y2FBNO!kkq;78ZQ|Y%}w&c3;@YkxxoWG3g%`D#Fj%^(&Yw*}b zv(szk{Gw3VLv7^~TC7<80^#iaDhTY(yYucbu^3krD`M>xydBTMJ?)JAC_T2L%JUl9 zP8;I0HDhdCbtRKdi73SLNPqfAM#Z3j!Z>O~*#n+Pjc+>pjyy*~5sy85&5eMwZKpZA zgMRnY{;p1G!B0asmtpOWj0v~Sfh6dt2eWX^Rm&k*KzXZ39KuNp?OeG@HPrZQ0N>O2d8Bha#d0N}x$~Wkc ze!0C_a)z(1Znx2u_^~Dbo}^6A{S(MPwQOW|mxayMMB*{z*grU`)Uxod&EX#%4E&xT zW;l=8W%Qi5MnQYWX~FB&_Dj3aiK!(n_^w1m+5pdBltyb@n))=~AECkMwpWO%y^U!K zD5B6_!_b&0MNsA1aXzaHbzc$@?a>JMT_(WC6mJ{7mIu6L$N5MRDLr^ZZe7j^k; zWA{dS^i0V?8^-wu6?ptrIDhJ!^xu97{#znhmceKLwILa9?<*2MtQ1}JykZ=pqF(QN zJBQ?H75Z@0&5yRGs|Z5y2s9Bjtgz5Vg~23*&ogi8ce#<(DraunzdN7)JY)rso;k#9 zvb@T+jQJV4L#<6Co) zmga2pudHQB-Ui19zY1GLjG4}u9HXWyHIaxxBubK)m=|Ze>iqp|4TuU>y5u;shz<$R z%n8`uWP?Q(@~85$`I<_aP^;gki!qMwn=TVAD;nI~@#XT7jZD>4UU?OdY?B>UO%snm zyb-kU5EZX2z$+ow`Z;j=dRDgn^BOMS*}8!vx<&aaEdLE*ahiG#PlJU{)ze)+UcVDYXiuZj4ghbPu?m=(=yp!- zLNy>BJvUtp!t_e&0)E8W>_TTz0N9 z2Kuy*D>;XxfOiEy$msQ@1YupMKg8mWq z*{^%1%oDO#a*Cymd(EM?dfQhi3&_r8a`+2o-K%^jCo1h8*i8#N?h9~77qYVfN!II2 z+8CR;D&4>Ik4dT9L zNOqfjY&5+Xq&*Bw#u*{Z9vCFCB8|4fPN^MbKVc1ckPTL_$xiihBjtIn5;;C1&m?`ABvwLi4#0 z(sTw3ca^N1EuWw%nof8?kcm^QXOu#^>KSTziN3ClZk7o6KxNL#+h!v66*z0P4!oly z8fA`XbazYT)ZrR6lp?e|MMgt%g*kmG>L2rKd%@&c;2@i=j6oebriMjgB%U*x`ULJR zAlv=phFi;UpKO!0*zjIa()j7n;7@AnE!i}e3oU`Qrs&ECGUpEzuraU!R2fQ@Zfif? z=?&AXv{sH|DBF*ku@5gQq`H-CTFm#*WE@kIcF~8C2Z^hn&6kA5O3*s&V-3~kDMTGy zC4*$PQuh|ZtsY4_p}rL}9Z8wIo1C6{jj7b)%K0{XD4?o`cBmAKxM+)SNh6)Fqt=)t!HJQ#rV)O@1t%x6H0TVJL!xX{c_KP8*#O0$~xbyvPJ%yP{C| zv8EW*Re}2h?k~rnpCx)m53g@AmN1LUa7s$prB~S?0S*3iTy_`3pi!8k@rLamQ(n>> zWizh1B@fpzgzyEPw$tyd#;5;I_~{4!M))uO4dEZJ)oUs43D)_%puzkX=XYyp!CpKd znEMU-3BUq8F__vkl7Er@g|D-0aRpeF;kD%9^%t>OKVD({Mf&65*#AZPwN@5CS{8zL z`vaUZF--=>T$87IL#`qYU)R?5*%R+4TZ%u*UL+(G<1pj&(7d1qvInnxDS#r_=}L4M z6W8RJ8Mufrd2@L6{jHadf+Ptce{`i~bTEPjk(71uq*pP@ir5~%O{>F2v@^Es&(f-Q zC2v(eZ;A5%qC7QXS1XKZEV5owCz=JmyrFpN_aYss_THc-;$N#t zh^Y7!AowZe1(I8d(=Nu;7Se1(kYCLfR2ossF?}1{5FC^C(%|N18Lgtyvv{aw?{um> z>w#gs7qOjm$wRf+zhFqTJvrJP35jjy$sQ3(RXo?!2eLyV0h%tFFWn}l16ynR1t=d| zOg#x85x;gBKx}xLtQUTXcm~{!SOs6hUu;Muv#ICbUw0ymEu|lQ-E9@$;Z9(-NXaV- zM(2(T(1h%)v{#7$F@*JqU3Wi_4`Y}C0FVeD{OcdVB%EJMeuP7;-{@gfGejc@*B^h~ z$jZ25Sfm4IJphT{rp-Y$`Y!Sc9G>?9XlE)xLvzK~vtWX8okxb*jv%K)sHZh~dF5+c zGSa^uj?Y!)w|ZL0pFDt=qQZL{$`}Ao z1!cuK2HmfdmZyM7n%C4YBJAzP)H8{;@@+&hQX2vM*7EY!e%@T`_yMGv8L0A7n~T1n!ok5pRteS5q@bh=x_X6t;w|>A)FasZ zjjx}S#iK=!=3K9-i}8Pe{ZLQJ!(PljG zyGTy19V)#$CkNunNI}LP?RJJcJ^cbxfNb*L0PRsf)zK9iuIIX^4 z`{6e70cI?VqW4{X{NpN%-?JbiURk7o$~8Pw8-XGRT4|8s`ixGO-3H{6D|om%{I}e!X{+k$_(O(C>kDW6F7KM zaJdr7St#?mL~fEy5|pt+vc1$E%c%OeKB-DU0z4VxP%%GNXLUnwG>JbZi~7rVer*j| zE};x^%@q(b_Z^bCmSyDp9Pu0R#}mK2@$5=T97RTM~#G%T>D^w@-Q|y^(@zAsdlc zz;)$A$~*N^ZUFrl-QQg)Sc-3?e*({@PBN%Y8qvp8^ey*zsSu}A10~yZfS8OHs$n;#@n4GpfRhd|1*~)_+>y;e$G37JKS`4WL9WIpIBlr zn&y_jM*gV+8<5dV+@nNT27iq|gJBxY)0)etQJ*>>8;t0gcKc@> z|DIxLv*<&s#O3+)lH*~mrlac+QNQaxVX^l)o4dnS7^u{Gj8EK4WCo32k7$)A!15SJ zuwo-@&WE!&is)XA5~yZB`vUXPfIwoHYsil3j@^U;xM@X)Ha{1d=bB)`IbsJOEw}D^ z^*@67ZHq?LYv4LLN`^u=RCzSs!&c?i- ztqiF5-EiT(vBqNTz=yt}jSAe%kwzADK@J`tg65+l@}A(%Euo(Yh(pLJOPl1|N3vKG!FAXVacB+Kk0K` zu&Yabnzv!i4;5)Rsd{K<(0&G!)D+0~DpcZOMBATJd^P&UP>W&MeWR*U6S6EIBR9&W z(K;|%2cnRn{%O8~qiPmLKZYsR<-K)`It7lb`&zQdhQ>;ouffELQ}y|Q@Fhix?s87& zUMjjksUI4!4OLDgTP0~?DtUD4nC28U1nLbD`Yy3KUNC_GJ#byIk;D_D@^2gfDF5jI zz!lut&wB#th3u{l#HpmREfmgytm!Sv>*rahOvr$~Dl?N#7q$h5tmaxd{@Y5r`GzbN zI8*}EL`8A;&}@eJCaK5^{ddL-w6MRvMecVkEz_F`nGef0CQz|1AV}|Bk*=U19Tborq$5p0Av9^yn{=i35;_PdC82i+y+c5HAjzG$ zwri`)ea^n;?ESmv9zR9j$c)Z8d7006#(xmH9&=i!1bNz2DuAQ8-|QW6>Yn55mMx0o z8jnliA8yb1sRV!&$og6LfB2e*)JBF>N@@7THNtOn-(As%rZKK1)j|U&{c$mua%BF^ z$G__QE}Lg{P^yD`&w5%Rei~0L=&?X@OnenL!lGnx%v)OoN{uf!3~0qye7nWuPRV}6 zU&#JFCzE$hw4V0vnL^>Ik@jgBjrVnwH}?tRTdu^(EUd12)w5pU&$JBZ@i24%(@BJN zP9%%Wv#5kptUte%)PM+m#H4P>@Jaldm12kMXKTwfFNK~`@G3n3o=cjb4{G;bAsQ~0 z+(A|=k?M_7o99fxZuB{Ko*MkxI!1Vwi;EP=Q5u3270|ujB|TR9jCJD>F>|7IMcD`P zw)kuL0mNfl$K|WvbtnmGMHLKDb_tI&`{->FH^4)u>#*^0H zT>$QJ=MZSZ#wU-(aAP>R2+d*cATAiRxdUF*6PkV-~L{HnX?෫I4)f^fhFb~xh%SK zR`579@}0AQ*7*!?h+`3XTcL3781`!D+hvhjQlj>E}jjfaT9n&{>8xNMH6W2e)gU8-Ea zJ?8Oo(1hcCYC31jymD!9j5J>D=T#(0A>SmEW8W6eKuYEIv!8k& zS_4K0F+#bAQi_Fe)O?T6r0O@yKPeGNk$R5k?8Hs_=CTCc|9g}FRZ)3WdHkNZ)>^_A zHGUb{D=-NdUp%T*IO$`OMDoN2PMSmBC)LLnHiA?&peRD9)!8?!wT{Qd&E$n+{jltT ztn(wPA8~^ocl8C~yRU_>Btt;lqp9f}=S6s@o(qq)v1T~#8)ii%NY6B%g$y=KalKi6 ze=72i2fcDBP%=@^*3wwWeIbHBK=KOwK!)JJ=SOV($Lq#Rz^5wuloXof``1NDB7hF? zmDzIsK%2k6i~SC;rZ{nACj6Vl|KAR{_!8l}yUX_6%sSL#1;L+^9SK7_6!4AYa~}Tz zHHMc@w)=Nw0F7TvZtXb$m3I!hmHz+|9@sKN0_Jl^{wwRSCu^!n=|RjFd!h*1mjfuA z&&L&X1JC`Rjba|r4E}=Do+tstjA=c_tzqw0lAdeOTQTv;&)fzDL@o!Qu751?mvb=D z?^3KBgCrz}fg;EVwhQ&o+;Q585x{iK$w^UQL#!G9ir+~5qW6HO^lijd+S>T@>&)xd z(Jr}Qt_6#(OhIG%u5SU+L!PFWzVW@W>yRdhDA(hUX&Fu(eoH)?zP2~H4u_v^P0my@gLSS@7g>->jjlGfUDVSF zEm2ED&yTY_iIy9o+ZMdq=09^t1X0k*&9ynfALoZK;SEPYZb#0@==`E8kDWTLSh>Cq z)v897-h$P22rbg4Bvq~uB^;`&_2mDd!apPu-faA!w%RTWmN7kiIC`;_P>)0@q2gq7 z!gr~1`9?j$z(S`SJgrsKb>RS(WqnYaGNs*o%m?H>Km=UTK1&OgpI^sOlH>E;3l3;I zPuHoG7`{4YD!xVj^KAnZbYFp|HI`d=h+J%`ZP=!pJYJlk4#_+0k*zTl)o&^ogEnf$ zO&%i$H`ID1Pq1|BDy{W6f*c|4TQ_OyEh_b0#5H8^8wAO|z8#x&Nb*US#^-(PiBsrp zhSxN-f^wuy&!23eOX;DuT&D1w!?2S3yjFY5$pmAQCh0iOZC}rUc(23(P<^c=PV6rr zI|^WuG$f>l*UqrsNCO%hGm&1L$U$$zCrWpLRtSo!>rS*byCvVeA<}{t_SLYsVt2!AwwVU%hxM9 zxk=oqD7eUOY#OyJ*}R0IGM)~@+>T)K5N4dSZ$QuSF)GP2GdyHz+^D0g>s_zSY6%_n z_*?wk%kvb{9l%SWnoHa7iZ9hv_ggTLYK zG0Y`ZGS@QUY?J!0wT-)H=$q~K1hff$x;y`PS2s{u;Z1Mx{pIhR{O-A5j($-qz81&vB8COfl5*GSyIyl#_#OPG2KIuhRKGd+ z8LYd&CnGm%R{rfG2`LxuPnW{?%OViCB3PPVC;WEtI=vy`-^-uhHzeRs$bobB;PrU{ z0BrwtT~fC3>aSgF--;Y8Kv^RdpUei!JBitLd$#q%rS`WpRF69*B9RBKL&?MHF9ixM zbI?IOOmM%uIh~@%?%BKpPFRu1X&X!u%1;}BX!k@ z(|7CQ@VaSQ2&fO7lt!R-oKztKWg*%g3S%v69cvFnuq<$}^F0?QXT%ttQsB?V)W7Ol zi|8PRkyiT6Dwe2iYW!D?Pk}AJXRig=)pAdFHNfLPEhR5qN%wsabdGteddUp+@i8Pl zcacxnYn>NWRMc_cx%ODtZGZ@3x#qCG8#KzSNKJ!SzhAiIy{!q;%a9`;VfH-O+;R9i znsQ|bP40o2l@}hE^oZoI-{DF^#d(wsJmhdRF0oE|Ro7P9cAf+0%A{$PghRTYbEl5! zc5r#ru|OZc-9bfe)MG*CPjD&A0DHI1T_H$S#a&g26T*MF#jh75`xlGfZnm*&wqI=z_o~WIxIwIB=YfXCv_ZG56Cj(hHD^f}VtO`sVt|K&C*o_WK`ta&HL%idR;t*59sU7rx!{a}BRMTDyCp$I}@+#U? z{=?)S*BSQdt9foDFj0FrAO-0nZF=BvPwYG4Pt6u@X+qbj{oUj*1WbN_@WWkR zL1`hB1jOB|w~nu8N*2!2Jk@KdpQyj*zaL#tBHzYvG5F+IP79mJo1IP~Qzf>6vr_m| z4aE3dhG%npJvhv6q&D3Wfcs^3+pLv*aj_Jsk5y#KXqU`~j<%^4jxtlo7s&E_QIlLRV zV5<;wW$vB#6$L7>@4vRToR?IY>D`YtxxlGP{_wwO^7q|>I2AJ0m)pM&lp*3sM(N`y=K`|N2{@PAH{6Fj#_`9;*fu{jwd{P5XAFz_(_&p7e7>x z(te3et{QP;6PCjv!%krJ#Jj7!WgoWZe9$-6E9S7!EZlp(bipi9jL+_=FPgZ0^8s8} zWY#(Ud8<`jShb-3kuz7B)CsQAxe-B5Xkw7~7^J$sa^Z`sTq*AU%P$usI6n^5A1~QC zSzp?g%$50=A2n7Ob)%fK))Xy5KBz91p7bVil`Y@j@=a8gC`d=29;savrL>EuSjqSn z=Jw%oVOfQn{tdUixZn*%WE4s|Il-5|pl^ok50iguPg^sd;Ag%c{EhDqZOV@p9UT=W z#DJ{&I(qL2h-m84xL@W^ksY^_vsXqYk=rb6;I(jN`|*2PHH?ql2oZ8s`2u(EmTT<4cLQmboNTR@t_SgX8w+9fvXU3n19|``B_;rW=>)x_EBG#d4aiYlAFy*fAiev z!xl0G(I;qLq5j-BSlpKvRJ-gmS|-scXz6=>80hgsJa$u*Vy-TE}gV{crW*o850jEemD6?+v(Wc ze6LOuWu0&9-p7tqOCge#^NY=2zxnCa#44JLgfE?I!dwCj{20G%Tv%i(5m8X^)@bGf z-Efo;s$(=}e>qGxTs5TFnX`LOF918jURDiud(6r2-TUi8%sC`|jrFgK9b#jh z$<Or?hXEsuw^VGxQ>Pz(+$6JaH7 zB48HvZRKmL@U3)XpE$%0a>KnA&PMAT5FBAPVcN!^(^O{iw?v+MDHqyR=793+i_E5c zQ+2WNN7EwaVX!s*r9xNaKguKEeHWdD&H}Niej=swmdU%F6u$HAPO>Of>f0ecd(WE9u;q+D824Gua_{A`IyX=df}ep)O0k?a{WO3K<}wD4y5yF6)OmBYFF120iNUT z$w{SJ?BfA3fev2$mkc$IGmnu2s=y6pK>zZn4Pg7oD>VA{{@lspa%;y1=>i8H;mgcA z4zO@|&z=nNh&5pfX+gEYnJ;h8nc~fsH7D-e2*TH(-fw7(7{Tp>;dM=!R=rdDCiIVz zh?)`0+7#2ogRhS`GVWZT&=#nO@&R-8eq4;QN3~r@BI4QefpR1{m%ZXKQU6Bul}9If zG?uWMb!`pkb;~9g0?sqSRRXrsx*uK&(xp+mMy>6!a9#99#t~M?hSM`nCGP^Q?mQY9 zBOQgM>K@KJ7(II734MBLBf_xDbe(r!+q{L|ZtGV06Yo1xf$?W=3=}s!*x9gfGD!xI zheO#ncR&9VOuQ`#8uG^RUW7k$7B_LA%F6S%WT)=_TgqLgdvSlb`!z#2wH&UDKDbzJz6f`_5H3< z6{Fy6ZiO!XkQqsx@AAIJpX{DXvB?}dm0_hKPeLQkd=A;?)^g?r&7lEME7ay0>U(FAEV;-+m zUckbmr3s21?a*IYRT!RF9^IP2_ON{872TkEn$q)_9xGRMIWO>IZ4*H@JG~hj<6<}C zTb-482_OP%caHrvWc4PG=KY4A^`W%^7>7lSyqy8x-KUN5aD{s2(yfbI#k9+ zM+c+slYI9Pg}I3Dym7XMm=?cuY)CK6qy*gP)iLs@n$9_*dwyr4X1uH-lqN4O!N5gB zdq#IecvvpQITIXKBE`2aOD1B!-vAW7C)FvJRZI2xMBUwV;3>0F@s^gYQEE0{6W<+r zmZIPD*ANY7x8|ILAd^fg5I-jK2ybcTy6a+erSoE*ow>2Y)|k%08zQf;<30t8`hn)R zgM_gGwW`8tIsG=p50N}Kmz15(6p6I6xGg?sW^;tDb8IMhIK*ke2O(7x;>Xsr%L1b1dH6T^o=u6tL|vU>$EmzDRE|lJ-#5jaueQUj_@}4e0WLRWi6eU0U|*{>a_G(uS{D2$9C`5 z&N`*ucY^P9)46E=&nzBfP`0;%4iQkwJ3z*&w+@xJT3@liJ%uc#%-5|rY&=}+RELmE zqIg&v0LuVo%)+A|BtZwDbgG+9SPz9YX0va-*}wazsy~xI9(p;;WiZ)O+_kRe5|g1s zCMCPZ9Ys$1j0&CskH{|{OqIl-wNM=d%3I)aikGr4j=k}Um4;3=ATnZ44m{zRfDvjLumf^YFz37ZZ{2dj%cO> z6n!y3(YLzR;0BB?Jz7^~X0My*x{eGBYCkC-DNd>3i9KlKvUs{tycf+fyDp zgtj>BKPMbsv3J=1bQW^zOTMs)dA~kZD-H6ac9I`_sQj}7>)m?u6v%s4ALtbJje3B3 zi~BiMo~WI;)az1hZNg*Kxz>D^ikyB>^do?-pwqyfQ`UbNl)ZdRLHB;`pGfSH5%>Z2 z3lMxxx6C36B}GMkWBrhS2kTF_@sjaUtU}VoPz|=?z?}8cb{tAL8(8=Z@FCT&`cNJY zsxeIMR#(fO!OY7yA~(-%R4p z&hOVhzl-B6-o(pIYGrt+=CCN`!6aP^QT>EobVIfp3SU(MjBXNqf5&wneQkv7g-EIG z#rmHV{g=bh@FcgbB#xJ0#Bt1+1_!;%m6iO9|1VJgGJyJ@{;x;<2ZcoodGr5dR*&+( zFsmoZclUvVr&MBvzC#f`W~Ifs(%c&U?<4&wU;jsB^)R4bmYg|r%}rib>Jfn3|F`A# zEGkaVwg&YQgwlxc=e(+B-b3uZ)mRQ&<;oXfMm2uO4%%m@3>Gt$m#|gq#xs%KEL>f$wc= z{y^fKCahJP?V}h3kpw-hU>$y=L(Z+?w>~UoNP`mt<0B7aO9y_k?7V)hYvN!OxEs8M*lFH z$7}iCpV2%ApbYEzO>0fIT-!}>gYVF#rTqzDV|-y9lM1ER8+Q6NNlFJ#~t3_w=YsCkAKI8C~e*Aw>J!~k0BIQuG`EyxK-~RH|%V~InpFc_!q1n{vTuge<<$%Cs_Y{+cR=r6Rb=>$>`7&HRdjOj1JALo?Z!-nuL)ukfb4G zHPy?9NpaK${M5WN3gg!-66Xi9v%l&;AeG9gq>Q z{^XxT`?->lv8K9Tqi^HKMt~c!1Iq$!ETjIg_lJw}4iG9CtBb*T#-dM~m=E;XxHhbq ze2iHC>g^}?4LHJ+BU-MPBf7_YmB)l37L~7ewRi&ZuG&wUv~4}cJ*KS;X*;hE(igfP z3bE$yU48WK8u{~aht>}YHSf&7iTmg;)|Ow4*+&LJbGx_)FP|ha?4^_yAC$@n^_EBM zLS^r1G<1XYp2aEL8F#Vlixn{t>KF~HlE~E{(7G~GgcWD$zmc+r*vu?X|Ha!ULP}8A z$avUqfZ^uJmrM7g7iV1-;7vWa+f-ba4d-eF?QbK^))tYT7Zsr*@xVi&qQ;@CJfpYm zp%Sz}nV#0nkp*bhIWFgRr^-C8LP5J#$Fu}L*7v=v(s&Zkxl^&gm{yos5ittPQKvUU z#aQZ?r?}`ZVie!pk)SwoJ_ZS{XzmlZ+FTr~Qt(%;;>b$3dek(mzSKY%@GYUo_G#fb zZakvlmXZFR@7Emz0Z#ZxMpXJU8>fOQ;lo>2;sHF4N4k-o%x`pD-SS!zDaQ_rQ8t$T zyCEIF59f*Rjq7&=lA)KS@3NoVm&163-8SHAW4nC6&CiltvPf*9x}KOQkl3_FYW!NS zS18nx%Va=URf}}y^*Ar+0>=bB38hksm~FBO?s20G^PLybl0jgSefhp3Mc|1N4(H<@ zo1?Q9`;w&ho^tC&;gAwZCYh7DA=Fa6(5J$Lex5q|KSBDT58Li48*m;jBJY*NURIAjhce3Ef*yP$=RX_y-O@# zcD{1pK9J61^0#!JZXh=$Ua}ERUC*G&mOQh#p;zD;k7BFS7sk8Ks|b&pi?}5ski95t zqJB^1J$@%A(Fgx(p~?rmao`6cekUl(wUUP^Rcw~WZ3HUC#I2~BclpUcjgZ_}u3Mp2 zfJCIU{$&V=0ZG_byPZa0+K5X$eb~XY{pBH?In!hB z7w<~Fmp_w~MkT$z8m4E0kV`Wq_q$>Fo#%%={~gb-5V?7$OSUNTQ2Ul%UfW1b%p$Vj z)7kAkmHfaRevUCG+)Z|ii%Af*e-qem2x0%0(4*4sVoMjys;&v7$!nsFneP~D({C=) zT=+=iS9qE5Htn{~MsN{H5Waj(fUgwz;DNTw6RC4w}B$(so9A=>WY2drk9dMybjxBZ~!!7}~GX_U7iU z+I1!N_vl%u=q$!>(`U5l0b@cCY;nXbZW)FOz}x{kq$ND{Twr;5dG435quc@L^ZyCn z|6SsK%8C5n!gFmsOY7KE5{5?Rqld`M`4Oh2G9 z0NDEWpMW0~5(7?ceL3Y-vx=^7zWx%|LY3tLl7Ia*xMybvu3)6`@IcoNmc@4GzsdBa zotB@af(=`ZFa+!Y*4KrWcv~{($77SgVV!={Qf}Anf;M_&aheL);_zmMAjSFQvabVF z_zDx(1T>bdVn0$i*sRn>>a6VF&9n+E6HYGuSYP`nOA2}=!E4yZ{mWt$m@4vJ3Nl(@ zjlsje+Ewk6yq&Jj)UyZ32A?<{tcH``Q?02(?>zl1)SB z{`R)083fJ=c$~fGcl6>Qp5}Dcy^P3lby>{e+$KG0Ek?DMIMWh6Sx|8A=GFDw zN2)d{a}DXBDR8Mb(dTkdSELtlGxhZfnUWYa5%Ec{ND$q&{e@Qe#~G)C%x&GsJ{zm6?*cqAXXV5)ybtlAdaep|`X)#Ol48kDj}RaRTucBs2rb3 z7*TpK*73D+x;0F*-SbR2)<-OF_)NDSaCRPZ3=MZ4Csrflg;CBg&DWO*oFZWYT3$nC z+m^DJ{?-e2Fky_7NLeKMya$@<=`|6|QA2eKC3+Hd(7RD)AQ2eQRvv?bKJu!ll)>C8 z+vcGa!45J?4q2K~BK$n^kqH2rOwy160D0Y>EnjoAsp$N-}QTr{C_6<(N` ze4R?L_{p5yN2|3c4Cc-4eyZI!5$C$Ih;kay{>xVzrtgm~wK*Kt?WSB?90vcQvo zTck}wAJIpjwH)a~MLs|$B8+=TJ}c1s%s)a}3|$~xtWKQcu_l$GbKHM>DO<^Lh;l?V zvQWDLhw!oNmUmvIn3d8?|Mn{z_4%LqaWGSpE$cEC)JusU<}2qZ8H+06Iis9rpYX1L zA&~eK5O<_0xs}4OXlDj^+7~fmNbIDAgaUb_vlgYKR+60^^G;gpk*W2pOWxy4!OaXrG|V0WsF31Jp7V~))-kQuEj2R)wIx(&(i z+Sf~`%OgWQ<_uKWS1#_)(g>lKKKP?_G3A`~>yf z0wVQ&le%O_iVK>8oG*L(Nmh9JTm_rua$Kr;cgST;dSmbHJKse_{}@B`aHGEcv;s|T z&gEz=;$_^6FQbed>;LRWV8zbay1T+2?tuHmC|txo;dH#fV4=;XQo2n;rA382!8`k5 zMXXtJW%H=`7_Fzo>M+|oqXRO>m1D^V8)lcox{pr>3(WXnfefs5vK$-uhsxIabJ{m= z%jq=PSSN+?^=jWt#`b0fBz=G(cgR@C2U+4zGm(;JDOZbM)qQ`yz#CO|#3Add2s5gk zRG8e)aMGoZP4?kRt#^V71PeX3(o3+{+ZH}0(oa~hs;vvi9+gZnCrq2TkmFDSP9nZ6 zj#r?=;|@;-7g47K5^!H}AFFwFG%#8~RL?`jrTI}A1ogxyP_ruEROPZjVcaVewWUyl zCdZ%ILn4;%IvQl=JhB+f6Q#AE6Ps#Rvyvw($nUjIy+sL~GiJfUk$j(c+K=FRXELdu zJ@@Ni6%CFB;CFsVee-Kr_^Iw`7yx{%C$mtw{$Dii)5Z6GfQwA2eF{{*tD=9th>iu; z7&qN{X@0Z#|JwoW>kb-v1-#r(Lm6Vwr;O$k5610pEeS!09s`=HdlFR`a#WANY}3+m zv%1{3o&@I=L_OeAmVgXDBTp$#=k7*I$xw=C_>_?AtAaeZs+XHhOxVg;ZRixf(0W8p zdi*a{p&)snB=j0(hp?B(!W1JoAeaFMhEDft{^?1~P@<0(g{XgQnxrrxG~nEH{xB1e zFbbaT`f;|v&7Woq6e<;Y3~YdwLFY9wb9U*F*y*E9#a-;5`Vzz;*@{P)n4jZ>_|iJZ z<;*u4mB*emNc*eOW{RBb|mU|vJ448(8ug9gleFI!Aj3agDXoFVo9=?dE?OS>uSkW}<`Q5}B!>)Syf)tZ* zvys(aF^aO9&U@#f)A;RhGj{IN=zkL)H?vh00 zLJg3A&2RyDCY`R3Y)UAU6Wl)(bWvko!sBq0KYyc!S2&EbF3Fn?=NfgM;8-6CPFOy@ zkL{s>zD>Q*KGW?CVdkhY>Ksu!4s<;>o!V*0m-##PihZr$C&x@XalD zx7&jL(&VI{FDUIBs;FZs%`ocvEImN;isWCKSL^`szDO4pGW5#@ern-M@&G5PdwJ{n zZ=8H~3Sg~s5N^itn?+t=)9VSoNc@|x+4#TZfUy2x$MOR&xv;FtVe71;u5 HL!bWx!QMMy literal 0 HcmV?d00001 diff --git a/luci-app-pingpacket/luci-app-pingpacket/.gitattributes b/luci-app-pingpacket/luci-app-pingpacket/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/luci-app-pingpacket/luci-app-pingpacket/Makefile b/luci-app-pingpacket/luci-app-pingpacket/Makefile new file mode 100644 index 000000000..40f9f77bf --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/Makefile @@ -0,0 +1,55 @@ +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI web interface for Pingpacket monitoring +LUCI_DEPENDS:=+luci-base +luci-lua-runtime +luci-compat +curl +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-pingpacket +PKG_VERSION:=1.0.0 +PKG_RELEASE:=20 +PKG_LICENSE:=Apache-2.0 + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller + $(INSTALL_DATA) ./luasrc/controller/pingpacket.lua $(1)/usr/lib/lua/luci/controller/pingpacket.lua + + $(INSTALL_DIR) $(1)/usr/lib/lua/luci/view/pingpacket + $(INSTALL_DATA) ./luasrc/view/pingpacket/status.htm $(1)/usr/lib/lua/luci/view/pingpacket/status.htm + $(INSTALL_DATA) ./luasrc/view/pingpacket/logs.htm $(1)/usr/lib/lua/luci/view/pingpacket/logs.htm + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/pingpacket $(1)/etc/config/pingpacket + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./root/etc/init.d/pingpacket $(1)/etc/init.d/pingpacket + + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./root/usr/bin/pingpacket.sh $(1)/usr/bin/pingpacket.sh + + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-pingpacket.json \ + $(1)/usr/share/rpcd/acl.d/luci-app-pingpacket.json +endef + +define Package/$(PKG_NAME)/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] && exit 0 +rm -f /tmp/luci-indexcache.* +rm -rf /tmp/luci-modulecache/ +/etc/init.d/rpcd reload 2>/dev/null +exit 0 +endef + +define Package/$(PKG_NAME)/prerm +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] && exit 0 +[ -f /etc/init.d/pingpacket ] && { + /etc/init.d/pingpacket disable 2>/dev/null + /etc/init.d/pingpacket stop 2>/dev/null +} +exit 0 +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/luci-app-pingpacket/luci-app-pingpacket/luasrc/controller/pingpacket.lua b/luci-app-pingpacket/luci-app-pingpacket/luasrc/controller/pingpacket.lua new file mode 100644 index 000000000..220a5901c --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/luasrc/controller/pingpacket.lua @@ -0,0 +1,518 @@ +module("luci.controller.pingpacket", package.seeall) + +local LOG_FILE = "/tmp/pingpacket.log" +local CPU_STATE_FILE = "/tmp/pingpacket_cpu_state" +local DEFAULT_PROXY_HOST = "127.0.0.1" +local function trim(value) + return (value or ""):gsub("%c", ""):match("^%s*(.-)%s*$") or "" +end + +local function normalize_enabled(value) + return value == "1" and "1" or "0" +end + +local function normalize_proxy_type(value) + return "socks5h" +end + +local function normalize_proxy_port(value) + value = trim(value) + + if value == "" then + return "" + end + + if not value:match("^%d+$") then + return nil + end + + local port = tonumber(value) + if not port or port < 1 or port > 65535 then + return nil + end + + return tostring(port) +end + +local function normalize_proxy_host(value) + value = trim(value) + + if value ~= "" then + return value + end + + return DEFAULT_PROXY_HOST +end + +local function normalize_probe_interval(value) + value = trim(value) + + if value == "" then + return "1" + end + + if not value:match("^%d+$") then + return nil + end + + local interval = tonumber(value) + if not interval or interval < 1 then + return nil + end + + return tostring(interval) +end + +local function normalize_foreign_probe_interval(value) + value = trim(value) + + if value == "" then + return "5" + end + + return normalize_probe_interval(value) +end + +local function write_json(payload) + local http = require("luci.http") + local jsonc = require("luci.jsonc") + + http.prepare_content("application/json") + http.write(jsonc.stringify(payload)) +end + +local function read_start_time() + local start_time_file = io.open("/var/run/pingpacket/start_time", "r") + if not start_time_file then + return "" + end + + local value = trim(start_time_file:read("*a")) + start_time_file:close() + return value +end + +local function should_require_target(request_source, enabled) + if request_source == "save" then + return true + end + + return enabled == "1" +end + +local function has_any_target(uci) + local domestic_target = trim(uci:get("pingpacket", "config", "domestic_target")) + local foreign_target = trim(uci:get("pingpacket", "config", "foreign_target")) + + return domestic_target ~= "" or foreign_target ~= "" +end + +local function has_valid_foreign_proxy(uci) + local foreign_target = trim(uci:get("pingpacket", "config", "foreign_target")) + local proxy_port = trim(uci:get("pingpacket", "config", "foreign_proxy_port")) + + if foreign_target == "" then + return true + end + + return proxy_port:match("^%d+$") and tonumber(proxy_port) and tonumber(proxy_port) >= 1 and tonumber(proxy_port) <= 65535 +end + +local function read_file_content(path) + local file = io.open(path, "r") + if not file then + return "" + end + + local content = file:read("*a") or "" + file:close() + return content +end + +local function read_log_payload() + local nixio_fs = require("nixio.fs") + local stat = nixio_fs.stat(LOG_FILE) + + return { + content = read_file_content(LOG_FILE), + updated_at = (stat and stat.mtime) and os.date("%Y-%m-%d %H:%M:%S", stat.mtime) or "", + size = (stat and stat.size) or 0 + } +end + +local function read_cpu_sample() + local file = io.open("/proc/stat", "r") + if not file then + return nil, nil + end + + local line = file:read("*l") or "" + file:close() + + local total = 0 + local idle = 0 + local index = 0 + + for token in line:gmatch("%S+") do + if token ~= "cpu" then + index = index + 1 + if index > 8 then + break + end + + local value = tonumber(token) or 0 + total = total + value + + if index == 4 or index == 5 then + idle = idle + value + end + end + end + + if total <= 0 then + return nil, nil + end + + return total, idle +end + +local function read_saved_cpu_sample() + local content = trim(read_file_content(CPU_STATE_FILE)) + local total, idle, timestamp = content:match("^(%d+)%s+(%d+)%s*(%d*)$") + + if not total or not idle then + return nil, nil, nil + end + + timestamp = tonumber(timestamp) + + return tonumber(total), tonumber(idle), timestamp +end + +local function write_cpu_sample(total, idle) + local file = io.open(CPU_STATE_FILE, "w") + if not file then + return + end + + file:write(string.format("%d %d %d\n", total, idle, os.time())) + file:close() +end + +local function format_percent(value) + value = tonumber(value) or 0 + + if value < 0 then + value = 0 + elseif value > 100 then + value = 100 + end + + return string.format("%.1f", value) +end + +local function read_memory_usage() + local file = io.open("/proc/meminfo", "r") + if not file then + return "0.0" + end + + local mem_total + local mem_available + local mem_free = 0 + local buffers = 0 + local cached = 0 + + for line in file:lines() do + local key, value = line:match("^(%w+):%s+(%d+)") + value = tonumber(value) + + if key == "MemTotal" then + mem_total = value + elseif key == "MemAvailable" then + mem_available = value + elseif key == "MemFree" then + mem_free = value or 0 + elseif key == "Buffers" then + buffers = value or 0 + elseif key == "Cached" then + cached = value or 0 + end + end + + file:close() + + if not mem_total or mem_total <= 0 then + return "0.0" + end + + if not mem_available then + mem_available = mem_free + buffers + cached + end + + return format_percent(((mem_total - mem_available) * 100) / mem_total) +end + +local function read_system_metrics() + local current_total, current_idle = read_cpu_sample() + local previous_total, previous_idle, previous_timestamp = read_saved_cpu_sample() + local cpu_usage = "0.0" + + if current_total and current_idle then + if not previous_timestamp or (os.time() - previous_timestamp) > 10 then + previous_total = nil + previous_idle = nil + end + + if previous_total and previous_idle and current_total > previous_total then + local total_delta = current_total - previous_total + local idle_delta = current_idle - previous_idle + + if total_delta > 0 then + cpu_usage = format_percent(((total_delta - idle_delta) * 100) / total_delta) + end + end + + write_cpu_sample(current_total, current_idle) + end + + return { + cpu_usage = cpu_usage, + memory_usage = read_memory_usage() + } +end + +local function validate_config(request_source, enabled, domestic, foreign, proxy_port) + if should_require_target(request_source, enabled) and domestic == "" and foreign == "" then + return false, "请至少填写一个监控目标。" + end + + if foreign ~= "" and proxy_port == "" then + return false, "已填写国外目标时,请同时填写代理端口。" + end + + return true, "" +end + +function index() + if not nixio.fs.access("/etc/config/pingpacket") then + return + end + + local page = entry( + {"admin", "status", "pingpacket"}, + alias("admin", "status", "pingpacket", "monitor"), + "Ping丢包监控", + 40 + ) + page.dependent = true + page.acl_depends = { "luci-app-pingpacket" } + + entry({"admin", "status", "pingpacket", "monitor"}, template("pingpacket/status"), "监控", 1).leaf = true + entry({"admin", "status", "pingpacket", "logs"}, template("pingpacket/logs"), "日志", 2).leaf = true + entry({"admin", "status", "pingpacket", "status"}, alias("admin", "status", "pingpacket", "monitor")).leaf = true + entry({"admin", "status", "pingpacket", "get_data"}, call("action_get_data")).leaf = true + entry({"admin", "status", "pingpacket", "get_logs"}, call("action_get_logs")).leaf = true + entry({"admin", "status", "pingpacket", "clear_logs"}, call("action_clear_logs")).leaf = true + entry({"admin", "status", "pingpacket", "save_config"}, call("action_save_config")).leaf = true + entry({"admin", "status", "pingpacket", "restart_service"}, call("action_restart_service")).leaf = true +end + +function action_get_data() + local uci = require("luci.model.uci").cursor() + local sys = require("luci.sys") + local jsonc = require("luci.jsonc") + + local enabled = normalize_enabled(uci:get("pingpacket", "config", "enabled")) + local domestic_target = trim(uci:get("pingpacket", "config", "domestic_target")) + local foreign_target = trim(uci:get("pingpacket", "config", "foreign_target")) + local foreign_proxy_type = normalize_proxy_type(uci:get("pingpacket", "config", "foreign_proxy_type")) + local foreign_proxy_host = normalize_proxy_host(uci:get("pingpacket", "config", "foreign_proxy_host")) + local foreign_proxy_port = trim(uci:get("pingpacket", "config", "foreign_proxy_port")) + local domestic_interval = normalize_probe_interval(uci:get("pingpacket", "config", "domestic_interval")) or "1" + local foreign_interval = normalize_foreign_probe_interval(uci:get("pingpacket", "config", "foreign_interval")) or "5" + + local result = { + enabled = enabled, + domestic_target = domestic_target, + foreign_target = foreign_target, + foreign_proxy_type = foreign_proxy_type, + foreign_proxy_host = foreign_proxy_host, + foreign_proxy_port = foreign_proxy_port, + domestic_interval = domestic_interval, + foreign_interval = foreign_interval, + server_time = os.date("%Y-%m-%d %H:%M:%S"), + start_time = read_start_time(), + updated_at = "", + service_running = (sys.call("[ -s /var/run/pingpacket/start_time ]") == 0), + system = read_system_metrics(), + domestic = { + avg = "0.0", + min = "0.0", + max = "0.0", + loss_rate = "0.0", + loss_count = 0, + count = 0, + samples = 0 + }, + foreign = { + avg = "0.0", + min = "0.0", + max = "0.0", + loss_rate = "0.0", + loss_count = 0, + count = 0, + samples = 0 + } + } + + local status_file = io.open("/tmp/pingpacket_status.json", "r") + if status_file then + local content = status_file:read("*a") + status_file:close() + + local ok, data = pcall(jsonc.parse, content) + if ok and data then + if data.start_time then + result.start_time = data.start_time + end + if data.updated_at then + result.updated_at = data.updated_at + end + if data.domestic then + result.domestic = data.domestic + end + if data.foreign then + result.foreign = data.foreign + end + end + end + + write_json(result) +end + +function action_get_logs() + local payload = read_log_payload() + payload.server_time = os.date("%Y-%m-%d %H:%M:%S") + write_json(payload) +end + +function action_clear_logs() + local nixio_fs = require("nixio.fs") + nixio_fs.remove(LOG_FILE) + + write_json({ + success = true, + message = "" + }) +end + +function action_save_config() + local uci = require("luci.model.uci").cursor() + local sys = require("luci.sys") + local http = require("luci.http") + + local enabled = normalize_enabled(http.formvalue("enabled")) + local domestic = trim(http.formvalue("domestic_target")) + local foreign = trim(http.formvalue("foreign_target")) + local proxy_type = normalize_proxy_type(http.formvalue("foreign_proxy_type")) + local proxy_host = DEFAULT_PROXY_HOST + local proxy_port = normalize_proxy_port(http.formvalue("foreign_proxy_port")) + local domestic_interval = normalize_probe_interval(http.formvalue("domestic_interval")) + local foreign_interval = normalize_foreign_probe_interval(http.formvalue("foreign_interval")) + local request_source = trim(http.formvalue("request_source")) + + if proxy_port == nil then + write_json({ + success = false, + message = "代理端口必须是 1 到 65535 之间的数字。" + }) + return + end + + if domestic_interval == nil then + write_json({ + success = false, + message = "国内 Ping间隔必须是大于 0 的整数秒。" + }) + return + end + + if foreign_interval == nil then + write_json({ + success = false, + message = "国外 Ping间隔必须是大于 0 的整数秒。" + }) + return + end + + local valid, message = validate_config(request_source, enabled, domestic, foreign, proxy_port) + if not valid then + write_json({ + success = false, + message = message + }) + return + end + + uci:set("pingpacket", "config", "enabled", enabled) + uci:set("pingpacket", "config", "domestic_target", domestic) + uci:set("pingpacket", "config", "foreign_target", foreign) + uci:set("pingpacket", "config", "foreign_proxy_type", proxy_type) + uci:set("pingpacket", "config", "foreign_proxy_host", proxy_host) + uci:set("pingpacket", "config", "foreign_proxy_port", proxy_port) + uci:set("pingpacket", "config", "domestic_interval", domestic_interval) + uci:set("pingpacket", "config", "foreign_interval", foreign_interval) + uci:commit("pingpacket") + + sys.call("rm -f /tmp/luci-indexcache*") + + local rc + if enabled == "1" then + rc = sys.call("/bin/sh /etc/init.d/pingpacket restart >/dev/null 2>&1") + else + rc = sys.call("/bin/sh /etc/init.d/pingpacket stop >/dev/null 2>&1") + end + + write_json({ + success = (rc == 0), + message = (rc == 0) and "" or "服务状态更新失败。" + }) +end + +function action_restart_service() + local uci = require("luci.model.uci").cursor() + local sys = require("luci.sys") + + local enabled = normalize_enabled(uci:get("pingpacket", "config", "enabled")) + + if enabled ~= "1" then + write_json({ + success = false, + message = "当前监控未启用。" + }) + return + end + + if not has_any_target(uci) then + write_json({ + success = false, + message = "请至少填写一个监控目标。" + }) + return + end + + if not has_valid_foreign_proxy(uci) then + write_json({ + success = false, + message = "国外目标已填写,但代理端口无效。" + }) + return + end + + local rc = sys.call("/bin/sh /etc/init.d/pingpacket restart >/dev/null 2>&1") + write_json({ + success = (rc == 0), + message = (rc == 0) and "" or "重新启动监控服务失败。" + }) +end diff --git a/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/logs.htm b/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/logs.htm new file mode 100644 index 000000000..7a4b6183a --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/logs.htm @@ -0,0 +1,477 @@ +<%+header%> + + + +

+ + + +<%+footer%> diff --git a/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/status.htm b/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/status.htm new file mode 100644 index 000000000..a797395b2 --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/luasrc/view/pingpacket/status.htm @@ -0,0 +1,1375 @@ +<%+header%> + + + +
+
+
+
OpenWrt Status
+

Ping丢包监控

+

国内目标继续使用 ICMP Ping,国外目标改为通过代理发起 `curl` 请求探测。保存并应用后,如果监控已启用,服务会自动重启并按最新配置重新开始累计统计。

+
+ +
+
+
+ 已关闭 + 监控未启用 +
+ + +
+
+
+ +
+
+ 开始时间 +
--
+
+
+ 运行时长 +
--
+
+
+ 最近更新 +
--
+
+
+ CPU 占用 +
--
+
+
+ 内存占用 +
--
+
+
+ +
+
+
+
+

国内目标

+
未配置
+
探测方式:ICMP Ping
+
+
+ 样本数 + 0 +
+
+ +
+
+ 平均延迟 +
+ -- + ms +
+
+
+ 最低延迟 +
+ -- + ms +
+
+
+ 最高延迟 +
+ -- + ms +
+
+
+ 丢包率 +
+ -- + % +
+
+
+ 丢包数 +
+ 0 + 次 +
+
+
+
+ +
+
+
+

国外目标

+
未配置
+
探测方式:代理 curl
+
+
+ 样本数 + 0 +
+
+ +
+
+ 平均延迟 +
+ -- + ms +
+
+
+ 最低延迟 +
+ -- + ms +
+
+
+ 最高延迟 +
+ -- + ms +
+
+
+ 丢包率 +
+ -- + % +
+
+
+ 丢包数 +
+ 0 + 次 +
+
+
+
+
+ +
+
+
+

监控设置

+
国外测速 URL 会通过本机代理发起 `curl` 请求统计延迟和丢包。代理主机固定为当前 OpenWrt 本机 `127.0.0.1`,探测方式固定按 `SOCKS5H`,你只需要设置代理端口。未写协议的国外目标会自动按 `https://` 处理。
+
+
+ +
+
+ + +
国内目标继续使用本机 ICMP Ping。可留空,但国内和国外不能同时留空。
+
+ +
+ + +
国外测速固定通过 `SOCKS5H` 代理执行 `curl` 探测。未填写协议时,系统会自动补成 `https://`。
+
+
+ + +
国内目标的探测间隔,单位为秒,默认 1 秒。
+
+ +
+ + +
国外目标的探测间隔,单位为秒,默认 5 秒。
+
+
+ +
+
+ + +
当前固定按 `SOCKS5H` 代理探测,域名解析也交给代理处理。
+
+ +
+ + +
OpenClash SOCKS5 端口常见为 7891,Sing-box SOCKS5 端口常见为 1080,具体端口自行查询代理插件,必须是 1 到 65535 的数字。
+
+
+ +
+
平均延迟、最低延迟、最高延迟、丢包率、丢包数和样本数均为本次服务启用后的累计统计值。停止或重启服务后,统计会重新开始;长期运行时样本数会持续增长并完整显示。
+
+ + + +
+
+
+
+ + + +<%+footer%> diff --git a/luci-app-pingpacket/luci-app-pingpacket/po/zh_Hans/pingpacket.po b/luci-app-pingpacket/luci-app-pingpacket/po/zh_Hans/pingpacket.po new file mode 100644 index 000000000..dc0d96f8f --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/po/zh_Hans/pingpacket.po @@ -0,0 +1,81 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" + +msgid "Ping Packet Loss Monitor" +msgstr "Ping丢包监控" + +msgid "Disabled" +msgstr "已关闭" + +msgid "Enabled" +msgstr "已启用" + +msgid "Start Time" +msgstr "开始时间" + +msgid "Duration" +msgstr "运行时长" + +msgid "Domestic" +msgstr "国内" + +msgid "Foreign" +msgstr "国外" + +msgid "Average" +msgstr "平均延迟" + +msgid "Minimum" +msgstr "最低延迟" + +msgid "Maximum" +msgstr "最高延迟" + +msgid "Packet Loss" +msgstr "丢包率" + +msgid "Not configured" +msgstr "未配置" + +msgid "Monitoring Settings" +msgstr "监控设置" + +msgid "Ping Domestic IP or Domain" +msgstr "Ping 国内 IP 或域名" + +msgid "e.g. 114.114.114.114 or baidu.com" +msgstr "例如:114.114.114.114 或 baidu.com" + +msgid "Leave empty to disable domestic monitoring." +msgstr "留空则不启用国内监控。" + +msgid "Ping Foreign IP or Domain" +msgstr "Ping 国外 IP 或域名" + +msgid "e.g. 8.8.8.8 or google.com" +msgstr "例如:8.8.8.8 或 google.com" + +msgid "Leave empty to disable foreign monitoring." +msgstr "留空则不启用国外监控。" + +msgid "Save & Apply" +msgstr "保存并应用" + +msgid "Saved successfully." +msgstr "保存成功。" + +msgid "Save failed." +msgstr "保存失败。" + +msgid "Ping Packet Loss" +msgstr "Ping丢包监控" + +msgid "Status" +msgstr "状态" + +msgid "Please configure at least one target." +msgstr "请至少配置一个目标。" + +msgid "Failed to update service state." +msgstr "服务状态更新失败。" diff --git a/luci-app-pingpacket/luci-app-pingpacket/root/etc/config/pingpacket b/luci-app-pingpacket/luci-app-pingpacket/root/etc/config/pingpacket new file mode 100644 index 000000000..c12720ae6 --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/root/etc/config/pingpacket @@ -0,0 +1,9 @@ +config pingpacket 'config' + option enabled '0' + option domestic_target 'www.baidu.com' + option foreign_target 'https://www.google.com/generate_204' + option foreign_proxy_type 'socks5h' + option foreign_proxy_host '127.0.0.1' + option foreign_proxy_port '7891' + option domestic_interval '1' + option foreign_interval '5' diff --git a/luci-app-pingpacket/luci-app-pingpacket/root/etc/init.d/pingpacket b/luci-app-pingpacket/luci-app-pingpacket/root/etc/init.d/pingpacket new file mode 100644 index 000000000..aca1f9704 --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/root/etc/init.d/pingpacket @@ -0,0 +1,103 @@ +#!/bin/sh /etc/rc.common + +START=90 +STOP=10 +USE_PROCD=1 + +PROG=/usr/bin/pingpacket.sh +RUN_DIR=/var/run/pingpacket +STATUS_FILE=/tmp/pingpacket_status.json +CONFIG_FILE=$RUN_DIR/config +LOG_FILE=/tmp/pingpacket.log +LOG_MAX_LINES=500 + +log_rotate() { + [ -f "$LOG_FILE" ] || return 0 + tail -n "$LOG_MAX_LINES" "$LOG_FILE" > "${LOG_FILE}.tmp" 2>/dev/null && \ + mv -f "${LOG_FILE}.tmp" "$LOG_FILE" +} + +log_event() { + local event_type="$1" + local message="$2" + local timestamp + + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + printf '[%s] [%s] %s\n' "$timestamp" "$event_type" "$message" >> "$LOG_FILE" + log_rotate +} + +format_foreign_summary() { + local target="$1" + local proxy_type="$2" + local proxy_host="$3" + local proxy_port="$4" + + [ -n "$target" ] || { + echo "未配置" + return + } + + if [ -n "$proxy_port" ]; then + echo "${target}(代理 ${proxy_type} 本机端口 ${proxy_port})" + else + echo "${target}(代理端口未配置)" + fi +} + +start_service() { + local enabled domestic foreign proxy_type proxy_host proxy_port domestic_interval foreign_interval foreign_summary + + config_load pingpacket + config_get enabled config enabled "0" + config_get domestic config domestic_target "" + config_get foreign config foreign_target "" + config_get proxy_type config foreign_proxy_type "socks5h" + config_get proxy_host config foreign_proxy_host "127.0.0.1" + config_get proxy_port config foreign_proxy_port "7891" + config_get domestic_interval config domestic_interval "1" + config_get foreign_interval config foreign_interval "5" + proxy_type="socks5h" + + [ "$enabled" = "1" ] || return 0 + [ -n "$domestic" ] || [ -n "$foreign" ] || return 0 + + rm -rf "$RUN_DIR" + rm -f "$STATUS_FILE" + mkdir -p "$RUN_DIR" + printf '%s\n' "$domestic" > "$CONFIG_FILE" + printf '%s\n' "$foreign" >> "$CONFIG_FILE" + printf '%s\n' "$proxy_type" >> "$CONFIG_FILE" + printf '%s\n' "${proxy_host:-127.0.0.1}" >> "$CONFIG_FILE" + printf '%s\n' "$proxy_port" >> "$CONFIG_FILE" + printf '%s\n' "$domestic_interval" >> "$CONFIG_FILE" + printf '%s\n' "$foreign_interval" >> "$CONFIG_FILE" + + foreign_summary="$(format_foreign_summary "$foreign" "$proxy_type" "$proxy_host" "$proxy_port")" + log_event "启动" "监控服务已启动,国内目标:${domestic:-未配置},国外目标:${foreign_summary}" + + procd_open_instance + procd_set_param command /bin/sh "$PROG" + procd_set_param respawn 3600 10 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +stop_service() { + if [ -d "$RUN_DIR" ] || [ -f "$STATUS_FILE" ]; then + log_event "停止" "监控服务已停止。" + fi + + rm -rf "$RUN_DIR" + rm -f "$STATUS_FILE" +} + +reload_service() { + stop + start +} + +service_triggers() { + procd_add_reload_trigger "pingpacket" +} diff --git a/luci-app-pingpacket/luci-app-pingpacket/root/usr/bin/pingpacket.sh b/luci-app-pingpacket/luci-app-pingpacket/root/usr/bin/pingpacket.sh new file mode 100644 index 000000000..8c797d99b --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/root/usr/bin/pingpacket.sh @@ -0,0 +1,682 @@ +#!/bin/sh + +STATUS_FILE="/tmp/pingpacket_status.json" +STATUS_TMP="${STATUS_FILE}.tmp" +LOG_FILE="/tmp/pingpacket.log" +LOG_MAX_LINES=500 +RUN_DIR="/var/run/pingpacket" +START_TIME_FILE="$RUN_DIR/start_time" +child_pids="" +domestic_fail_count=0 +foreign_fail_count=0 +domestic_fault_open=0 +foreign_fault_open=0 +domestic_total_samples=0 +foreign_total_samples=0 +domestic_success_count=0 +foreign_success_count=0 +domestic_loss_count=0 +foreign_loss_count=0 +domestic_rtt_sum="0" +foreign_rtt_sum="0" +domestic_min_rtt="" +foreign_min_rtt="" +domestic_max_rtt="" +foreign_max_rtt="" +domestic_last_probe_at=0 +foreign_last_probe_at=0 +domestic_last_fault_probe_at=0 +foreign_last_fault_probe_at=0 +domestic_last_stats_probe_at=0 +foreign_last_stats_probe_at=0 + +log_rotate() { + [ -f "$LOG_FILE" ] || return 0 + tail -n "$LOG_MAX_LINES" "$LOG_FILE" > "${LOG_FILE}.tmp" 2>/dev/null && \ + mv -f "${LOG_FILE}.tmp" "$LOG_FILE" +} + +log_event() { + local event_type="$1" + local scope="$2" + local message="$3" + local timestamp + + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + if [ -n "$scope" ]; then + printf '[%s] [%s] [%s] %s\n' "$timestamp" "$event_type" "$scope" "$message" >> "$LOG_FILE" + else + printf '[%s] [%s] %s\n' "$timestamp" "$event_type" "$message" >> "$LOG_FILE" + fi + log_rotate +} + +kill_children() { + [ -n "$child_pids" ] || return 0 + kill $child_pids 2>/dev/null + wait $child_pids 2>/dev/null + child_pids="" +} + +cleanup() { + kill_children + exit 0 +} + +trap cleanup INT TERM + +mkdir -p "$RUN_DIR" +date '+%Y-%m-%d %H:%M:%S' > "$START_TIME_FILE" + +write_result_file() { + local path="$1" + local success="$2" + local rtt="$3" + local detail="$4" + local probe_at="$5" + + [ -n "$probe_at" ] || probe_at="$(date '+%s' 2>/dev/null || echo 0)" + + mkdir -p "$(dirname "$path")" + printf '%s\n%s\n%s\n%s\n' "$success" "$rtt" "$detail" "$probe_at" > "$path" +} + +normalize_foreign_url() { + case "$1" in + http://*|https://*) + printf '%s\n' "$1" + ;; + *) + printf 'https://%s\n' "$1" + ;; + esac +} + +ensure_runtime_state() { + local domestic + local foreign + local proxy_type + local proxy_host + local proxy_port + local domestic_interval + local foreign_interval + + mkdir -p "$RUN_DIR" + + if [ ! -s "$START_TIME_FILE" ]; then + date '+%Y-%m-%d %H:%M:%S' > "$START_TIME_FILE" + fi + + if [ -f "$RUN_DIR/config" ]; then + return 0 + fi + + if ! command -v uci >/dev/null 2>&1; then + return 0 + fi + + domestic="$(uci -q get pingpacket.config.domestic_target)" + foreign="$(uci -q get pingpacket.config.foreign_target)" + proxy_type="$(uci -q get pingpacket.config.foreign_proxy_type)" + proxy_host="$(uci -q get pingpacket.config.foreign_proxy_host)" + proxy_port="$(uci -q get pingpacket.config.foreign_proxy_port)" + domestic_interval="$(uci -q get pingpacket.config.domestic_interval)" + foreign_interval="$(uci -q get pingpacket.config.foreign_interval)" + + proxy_type="socks5h" + [ -n "$proxy_host" ] || proxy_host="127.0.0.1" + [ -n "$proxy_port" ] || proxy_port="7891" + [ -n "$domestic_interval" ] || domestic_interval="1" + [ -n "$foreign_interval" ] || foreign_interval="5" + + printf '%s\n' "$domestic" > "$RUN_DIR/config" + printf '%s\n' "$foreign" >> "$RUN_DIR/config" + printf '%s\n' "$proxy_type" >> "$RUN_DIR/config" + printf '%s\n' "$proxy_host" >> "$RUN_DIR/config" + printf '%s\n' "$proxy_port" >> "$RUN_DIR/config" + printf '%s\n' "$domestic_interval" >> "$RUN_DIR/config" + printf '%s\n' "$foreign_interval" >> "$RUN_DIR/config" +} + +normalize_interval() { + local value="$1" + + case "$value" in + ""|*[!0-9]*) + printf '1\n' + return 0 + ;; + esac + + if [ "$value" -ge 1 ] 2>/dev/null; then + printf '%s\n' "$value" + else + printf '1\n' + fi +} + +should_probe_now() { + local last_probe_at="${1:-0}" + local interval="${2:-1}" + local now_ts="${3:-0}" + + if [ "$last_probe_at" -le 0 ] 2>/dev/null; then + return 0 + fi + + if [ $((now_ts - last_probe_at)) -ge "$interval" ] 2>/dev/null; then + return 0 + fi + + return 1 +} + +is_ip_literal() { + case "$1" in + *:*|*.*) + printf '%s\n' "$1" | awk ' + /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { found=1 } + /^[0-9A-Fa-f:]+$/ { found=1 } + END { exit(found ? 0 : 1) } + ' + ;; + *) + return 1 + ;; + esac +} + +resolve_target_ip() { + local target="$1" + local name="$2" + local cache_target_file="$RUN_DIR/${name}_resolved_target" + local cache_ip_file="$RUN_DIR/${name}_resolved_ip" + local cache_time_file="$RUN_DIR/${name}_resolved_at" + local cached_target + local cached_ip + local cached_time + local now + local resolved_ip="" + + if is_ip_literal "$target"; then + printf '%s\n' "$target" + return 0 + fi + + cached_target="$(cat "$cache_target_file" 2>/dev/null || echo "")" + cached_ip="$(cat "$cache_ip_file" 2>/dev/null || echo "")" + cached_time="$(cat "$cache_time_file" 2>/dev/null || echo "")" + now="$(date '+%s' 2>/dev/null || echo 0)" + + if [ "$cached_target" = "$target" ] && [ -n "$cached_ip" ] && [ -n "$cached_time" ]; then + if [ $((now - cached_time)) -lt 300 ] 2>/dev/null; then + printf '%s\n' "$cached_ip" + return 0 + fi + fi + + if command -v resolveip >/dev/null 2>&1; then + resolved_ip="$( + resolveip -4 -t 3 "$target" 2>/dev/null | awk ' + { + for (i = 1; i <= NF; i++) { + if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { + print $i + exit + } + } + } + ' + )" + fi + + if [ -z "$resolved_ip" ] && command -v nslookup >/dev/null 2>&1; then + resolved_ip="$( + nslookup "$target" 2>/dev/null | awk ' + { + for (i = 1; i <= NF; i++) { + if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { + print $i + exit + } + } + } + ' + )" + fi + + if [ -n "$resolved_ip" ]; then + printf '%s\n' "$target" > "$cache_target_file" + printf '%s\n' "$resolved_ip" > "$cache_ip_file" + printf '%s\n' "$now" > "$cache_time_file" + printf '%s\n' "$resolved_ip" + return 0 + fi + + if [ "$cached_target" = "$target" ] && [ -n "$cached_ip" ]; then + printf '%s\n' "$cached_ip" + return 0 + fi + + printf '%s\n' "$target" +} + +do_ping() { + local target="$1" + local name="$2" + local result_file="$RUN_DIR/${name}_last_result" + local ping_target + local result + local rtt + + ping_target="$(resolve_target_ip "$target" "$name")" + result="$(ping -n -c 1 -W 2 "$ping_target" 2>/dev/null)" + if echo "$result" | grep -q "bytes from"; then + rtt="$(echo "$result" | sed -n 's/.*time=\([0-9.]*\).*/\1/p')" + write_result_file "$result_file" "1" "${rtt:-0}" "" + else + write_result_file "$result_file" "0" "" "ping 无响应" + fi +} + +do_proxy_curl() { + local target="$1" + local proxy_type="$2" + local proxy_host="${3:-127.0.0.1}" + local proxy_port="$4" + local name="$5" + local result_file="$RUN_DIR/${name}_last_result" + local error_file="$RUN_DIR/${name}_curl_error" + local url + local proxy_addr + local curl_result + local http_code + local time_pretransfer + local rtt_ms + local detail + local rc + + if ! command -v curl >/dev/null 2>&1; then + write_result_file "$result_file" "0" "" "系统未安装 curl" + return 0 + fi + + if [ -z "$proxy_port" ]; then + write_result_file "$result_file" "0" "" "代理端口未配置" + return 0 + fi + + url="$(normalize_foreign_url "$target")" + proxy_addr="${proxy_host}:${proxy_port}" + + curl_result="$( + curl -k -I -L -sS --noproxy "" \ + --connect-timeout 5 --max-time 10 \ + --socks5-hostname "$proxy_addr" \ + -o /dev/null -w '%{http_code}:%{time_pretransfer}' \ + "$url" 2>"$error_file" + )" + rc=$? + + http_code="$(printf '%s' "$curl_result" | awk -F ':' 'NR==1 { print $1 }')" + time_pretransfer="$(printf '%s' "$curl_result" | awk -F ':' 'NR==1 { print $2 }')" + + if [ "$rc" -eq 0 ] && [ -n "$http_code" ] && [ -n "$time_pretransfer" ] && { [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; }; then + rtt_ms="$(awk -v sec="$time_pretransfer" 'BEGIN { printf "%.1f", (sec + 0) * 1000 }')" + write_result_file "$result_file" "1" "$rtt_ms" "curl 连接探测成功,HTTP ${http_code}" + else + detail="$(sed -n '1p' "$error_file" 2>/dev/null)" + [ -n "$detail" ] || detail="curl 退出码 ${rc},HTTP ${http_code:-000}" + write_result_file "$result_file" "0" "" "$detail" + fi + + rm -f "$error_file" +} + +get_target_state() { + local name="$1" + + case "$name" in + domestic) + fail_count="$domestic_fail_count" + fault_open="$domestic_fault_open" + ;; + foreign) + fail_count="$foreign_fail_count" + fault_open="$foreign_fault_open" + ;; + *) + fail_count=0 + fault_open=0 + ;; + esac +} + +set_target_state() { + local name="$1" + local next_fail_count="$2" + local next_fault_open="$3" + + case "$name" in + domestic) + domestic_fail_count="$next_fail_count" + domestic_fault_open="$next_fault_open" + ;; + foreign) + foreign_fail_count="$next_fail_count" + foreign_fault_open="$next_fault_open" + ;; + esac +} + +get_processed_probe_at() { + local name="$1" + local kind="$2" + + case "${name}:${kind}" in + domestic:fault) + processed_probe_at="$domestic_last_fault_probe_at" + ;; + foreign:fault) + processed_probe_at="$foreign_last_fault_probe_at" + ;; + domestic:stats) + processed_probe_at="$domestic_last_stats_probe_at" + ;; + foreign:stats) + processed_probe_at="$foreign_last_stats_probe_at" + ;; + *) + processed_probe_at=0 + ;; + esac +} + +set_processed_probe_at() { + local name="$1" + local kind="$2" + local value="${3:-0}" + + case "${name}:${kind}" in + domestic:fault) + domestic_last_fault_probe_at="$value" + ;; + foreign:fault) + foreign_last_fault_probe_at="$value" + ;; + domestic:stats) + domestic_last_stats_probe_at="$value" + ;; + foreign:stats) + foreign_last_stats_probe_at="$value" + ;; + esac +} + +reset_target_state() { + local name="$1" + + rm -f "$RUN_DIR/${name}_last_result" + set_target_state "$name" 0 0 + set_processed_probe_at "$name" "fault" 0 + set_processed_probe_at "$name" "stats" 0 +} + +update_target_faults() { + local name="$1" + local label="$2" + local target="$3" + local result_file="$RUN_DIR/${name}_last_result" + local success="0" + local rtt="" + local detail="" + local probe_at="" + + [ -n "$target" ] || { + reset_target_state "$name" + return 0 + } + + get_target_state "$name" + + if [ -f "$result_file" ]; then + success="$(sed -n '1p' "$result_file" 2>/dev/null)" + rtt="$(sed -n '2p' "$result_file" 2>/dev/null)" + detail="$(sed -n '3p' "$result_file" 2>/dev/null)" + probe_at="$(sed -n '4p' "$result_file" 2>/dev/null)" + fi + + get_processed_probe_at "$name" "fault" + if [ -n "$probe_at" ] && [ "$probe_at" = "$processed_probe_at" ]; then + return 0 + fi + + if [ "$success" = "1" ]; then + if [ "$fault_open" = "1" ]; then + log_event "恢复" "$label" "目标 ${target} 已恢复响应,当前延迟 ${rtt:-0} ms" + fi + + set_target_state "$name" 0 0 + set_processed_probe_at "$name" "fault" "${probe_at:-0}" + return 0 + fi + + fail_count=$((fail_count + 1)) + if [ -n "$detail" ]; then + log_event "丢包" "$label" "目标 ${target} 本次探测失败(连续第 ${fail_count} 次):${detail}" + else + log_event "丢包" "$label" "目标 ${target} 本次探测失败(连续第 ${fail_count} 次)" + fi + fault_open=1 + + set_target_state "$name" "$fail_count" "$fault_open" + set_processed_probe_at "$name" "fault" "${probe_at:-0}" +} + +calc_stats() { + local name="$1" + local total_samples=0 + local success_count=0 + local loss_count=0 + local rtt_sum="0" + local min_rtt="" + local max_rtt="" + local avg="0.0" + local min="0.0" + local max="0.0" + local loss_rate="0.0" + + case "$name" in + domestic) + total_samples="$domestic_total_samples" + success_count="$domestic_success_count" + loss_count="$domestic_loss_count" + rtt_sum="$domestic_rtt_sum" + min_rtt="$domestic_min_rtt" + max_rtt="$domestic_max_rtt" + ;; + foreign) + total_samples="$foreign_total_samples" + success_count="$foreign_success_count" + loss_count="$foreign_loss_count" + rtt_sum="$foreign_rtt_sum" + min_rtt="$foreign_min_rtt" + max_rtt="$foreign_max_rtt" + ;; + esac + + if [ "$success_count" -gt 0 ]; then + avg="$(awk -v sum="$rtt_sum" -v count="$success_count" 'BEGIN { printf "%.1f", (sum + 0) / count }')" + min="$(awk -v value="${min_rtt:-0}" 'BEGIN { printf "%.1f", value + 0 }')" + max="$(awk -v value="${max_rtt:-0}" 'BEGIN { printf "%.1f", value + 0 }')" + fi + + if [ "$total_samples" -gt 0 ]; then + loss_rate="$(awk -v loss="$loss_count" -v total="$total_samples" 'BEGIN { printf "%.1f", loss * 100.0 / total }')" + fi + + printf '{"avg":"%s","min":"%s","max":"%s","loss_rate":"%s","loss_count":%d,"count":%d,"samples":%d}' \ + "$avg" "$min" "$max" "$loss_rate" "$loss_count" "$success_count" "$total_samples" +} + +update_cumulative_stats() { + local name="$1" + local target="$2" + local result_file="$RUN_DIR/${name}_last_result" + local success="0" + local rtt="" + local probe_at="" + local total_samples=0 + local success_count=0 + local loss_count=0 + local rtt_sum="0" + local min_rtt="" + local max_rtt="" + + [ -n "$target" ] || return 0 + + case "$name" in + domestic) + total_samples="$domestic_total_samples" + success_count="$domestic_success_count" + loss_count="$domestic_loss_count" + rtt_sum="$domestic_rtt_sum" + min_rtt="$domestic_min_rtt" + max_rtt="$domestic_max_rtt" + ;; + foreign) + total_samples="$foreign_total_samples" + success_count="$foreign_success_count" + loss_count="$foreign_loss_count" + rtt_sum="$foreign_rtt_sum" + min_rtt="$foreign_min_rtt" + max_rtt="$foreign_max_rtt" + ;; + esac + + total_samples=$((total_samples + 1)) + + if [ -f "$result_file" ]; then + success="$(sed -n '1p' "$result_file" 2>/dev/null)" + rtt="$(sed -n '2p' "$result_file" 2>/dev/null)" + probe_at="$(sed -n '4p' "$result_file" 2>/dev/null)" + fi + + get_processed_probe_at "$name" "stats" + if [ -n "$probe_at" ] && [ "$probe_at" = "$processed_probe_at" ]; then + return 0 + fi + + if [ "$success" = "1" ]; then + rtt="${rtt:-0}" + success_count=$((success_count + 1)) + rtt_sum="$(awk -v sum="$rtt_sum" -v value="$rtt" 'BEGIN { printf "%.3f", (sum + 0) + (value + 0) }')" + + if [ -z "$min_rtt" ]; then + min_rtt="$rtt" + else + min_rtt="$(awk -v current="$min_rtt" -v value="$rtt" 'BEGIN { printf "%.3f", ((value + 0) < (current + 0)) ? (value + 0) : (current + 0) }')" + fi + + if [ -z "$max_rtt" ]; then + max_rtt="$rtt" + else + max_rtt="$(awk -v current="$max_rtt" -v value="$rtt" 'BEGIN { printf "%.3f", ((value + 0) > (current + 0)) ? (value + 0) : (current + 0) }')" + fi + else + loss_count=$((loss_count + 1)) + fi + + case "$name" in + domestic) + domestic_total_samples="$total_samples" + domestic_success_count="$success_count" + domestic_loss_count="$loss_count" + domestic_rtt_sum="$rtt_sum" + domestic_min_rtt="$min_rtt" + domestic_max_rtt="$max_rtt" + ;; + foreign) + foreign_total_samples="$total_samples" + foreign_success_count="$success_count" + foreign_loss_count="$loss_count" + foreign_rtt_sum="$rtt_sum" + foreign_min_rtt="$min_rtt" + foreign_max_rtt="$max_rtt" + ;; + esac + + set_processed_probe_at "$name" "stats" "${probe_at:-0}" +} + +while true; do + ensure_runtime_state + START_TIME="$(cat "$START_TIME_FILE" 2>/dev/null || echo "")" + UPDATED_AT="$(date '+%Y-%m-%d %H:%M:%S')" + NOW_TS="$(date '+%s' 2>/dev/null || echo 0)" + DOMESTIC_TARGET="" + FOREIGN_TARGET="" + FOREIGN_PROXY_TYPE="socks5h" + FOREIGN_PROXY_HOST="127.0.0.1" + FOREIGN_PROXY_PORT="7891" + DOMESTIC_INTERVAL="1" + FOREIGN_INTERVAL="5" + + if [ -f "$RUN_DIR/config" ]; then + DOMESTIC_TARGET="$(sed -n '1p' "$RUN_DIR/config")" + FOREIGN_TARGET="$(sed -n '2p' "$RUN_DIR/config")" + FOREIGN_PROXY_TYPE="$(sed -n '3p' "$RUN_DIR/config")" + FOREIGN_PROXY_HOST="$(sed -n '4p' "$RUN_DIR/config")" + FOREIGN_PROXY_PORT="$(sed -n '5p' "$RUN_DIR/config")" + DOMESTIC_INTERVAL="$(sed -n '6p' "$RUN_DIR/config")" + FOREIGN_INTERVAL="$(sed -n '7p' "$RUN_DIR/config")" + fi + + FOREIGN_PROXY_TYPE="socks5h" + [ -n "$FOREIGN_PROXY_HOST" ] || FOREIGN_PROXY_HOST="127.0.0.1" + [ -n "$FOREIGN_PROXY_PORT" ] || FOREIGN_PROXY_PORT="7891" + [ -n "$FOREIGN_INTERVAL" ] || FOREIGN_INTERVAL="5" + DOMESTIC_INTERVAL="$(normalize_interval "$DOMESTIC_INTERVAL")" + FOREIGN_INTERVAL="$(normalize_interval "$FOREIGN_INTERVAL")" + + child_pids="" + if [ -n "$DOMESTIC_TARGET" ]; then + if should_probe_now "$domestic_last_probe_at" "$DOMESTIC_INTERVAL" "$NOW_TS"; then + do_ping "$DOMESTIC_TARGET" "domestic" & + child_pids="$!" + domestic_last_probe_at="$NOW_TS" + fi + else + domestic_last_probe_at=0 + reset_target_state "domestic" + fi + + if [ -n "$FOREIGN_TARGET" ]; then + if should_probe_now "$foreign_last_probe_at" "$FOREIGN_INTERVAL" "$NOW_TS"; then + do_proxy_curl "$FOREIGN_TARGET" "$FOREIGN_PROXY_TYPE" "$FOREIGN_PROXY_HOST" "$FOREIGN_PROXY_PORT" "foreign" & + child_pids="${child_pids:+$child_pids }$!" + foreign_last_probe_at="$NOW_TS" + fi + else + foreign_last_probe_at=0 + reset_target_state "foreign" + fi + + [ -n "$child_pids" ] && wait $child_pids 2>/dev/null + child_pids="" + + update_target_faults "domestic" "国内" "$DOMESTIC_TARGET" + update_target_faults "foreign" "国外" "$FOREIGN_TARGET" + update_cumulative_stats "domestic" "$DOMESTIC_TARGET" + update_cumulative_stats "foreign" "$FOREIGN_TARGET" + + DOMESTIC_STATS="$(calc_stats "domestic")" + FOREIGN_STATS="$(calc_stats "foreign")" + + printf '{"start_time":"%s","updated_at":"%s","domestic":%s,"foreign":%s}\n' \ + "$START_TIME" "$UPDATED_AT" "$DOMESTIC_STATS" "$FOREIGN_STATS" > "$STATUS_TMP" && \ + mv -f "$STATUS_TMP" "$STATUS_FILE" + + sleep 1 +done + diff --git a/luci-app-pingpacket/luci-app-pingpacket/root/usr/share/rpcd/acl.d/luci-app-pingpacket.json b/luci-app-pingpacket/luci-app-pingpacket/root/usr/share/rpcd/acl.d/luci-app-pingpacket.json new file mode 100644 index 000000000..3249405f2 --- /dev/null +++ b/luci-app-pingpacket/luci-app-pingpacket/root/usr/share/rpcd/acl.d/luci-app-pingpacket.json @@ -0,0 +1,15 @@ +{ + "luci-app-pingpacket": { + "description": "Grant access to luci-app-pingpacket", + "read": { + "uci": [ + "pingpacket" + ] + }, + "write": { + "uci": [ + "pingpacket" + ] + } + } +} diff --git a/luci-app-qemu-vms/Makefile b/luci-app-qemu-vms/Makefile index 644d9d532..37e167002 100644 --- a/luci-app-qemu-vms/Makefile +++ b/luci-app-qemu-vms/Makefile @@ -1,10 +1,10 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Web UI for QEMU Virtual Machine Simple (VMS) -LUCI_DEPENDS:=+qemu-vms +ttyd +usbutils +pciutils +LUCI_DEPENDS:=@TARGET_x86_64 +qemu-vms +ttyd +usbutils +pciutils PKG_LICENSE:=GPLv3 PKG_VERSION:=0.0.1 -PKG_RELEASE:=2 +PKG_RELEASE:=3 include $(TOPDIR)/feeds/luci/luci.mk diff --git a/qemu-vms/Makefile b/qemu-vms/Makefile index c979f7a4e..6338f973d 100644 --- a/qemu-vms/Makefile +++ b/qemu-vms/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=qemu-vms PKG_VERSION:=0.0.1 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_MAINTAINER:=Konstantine Shevlakov include $(INCLUDE_DIR)/package.mk @@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) SECTION:=utils CATEGORY:=Utilities - PKGARCH:=all TITLE:=QEMU Virtual Machines Simple (VMS) - DEPENDS:= +qemu-x86_64-softmmu \ - +qemu-bridge-helper \ - +minicom \ - +socat + DEPENDS:= @TARGET_x86_64 \ + +qemu-x86_64-softmmu \ + +qemu-bridge-helper \ + +minicom \ + +socat endef define Package/$(PKG_NAME)/description