mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 22:33:56 +08:00
🌈 Sync 2026-07-29 14:47:30
This commit is contained in:
1 parent
d31b90c3d6
commit
9419456fd2
48 files changed
+13901
-3399
No files matched your search
@@ -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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 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.
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
#
|
#
|
||||||
# Copyright (C) 2022-2024 ImmortalWrt.org
|
# Copyright (C) 2022-2023 ImmortalWrt.org
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
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:= \
|
LUCI_DEPENDS:= \
|
||||||
+sing-box \
|
+sing-box \
|
||||||
+firewall4 \
|
+firewall4 \
|
||||||
|
+dnsmasq-full \
|
||||||
|
+ip-full \
|
||||||
|
+kmod-tun \
|
||||||
+kmod-nft-tproxy \
|
+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
|
define Package/luci-app-homeproxy/conffiles
|
||||||
/etc/config/homeproxy
|
/etc/config/homeproxy
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -12,6 +12,158 @@
|
|||||||
'require uci';
|
'require uci';
|
||||||
'require ui';
|
'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({
|
return baseclass.extend({
|
||||||
dns_strategy: {
|
dns_strategy: {
|
||||||
'': _('Default'),
|
'': _('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) {
|
calcStringMD5(e) {
|
||||||
/* Thanks to https://stackoverflow.com/a/41602636 */
|
/* Thanks to https://stackoverflow.com/a/41602636 */
|
||||||
let h = (a, b) => {
|
let h = (a, b) => {
|
||||||
@@ -185,6 +360,49 @@ return baseclass.extend({
|
|||||||
return L.resolveDefault(callGetSingBoxFeatures(), {});
|
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) {
|
generateRand(type, length) {
|
||||||
let byteArr;
|
let byteArr;
|
||||||
if (['base64', 'hex'].includes(type))
|
if (['base64', 'hex'].includes(type))
|
||||||
@@ -208,6 +426,9 @@ return baseclass.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadDefaultLabel(uciconfig, ucisection) {
|
loadDefaultLabel(uciconfig, ucisection) {
|
||||||
|
if (!ucisection)
|
||||||
|
return '';
|
||||||
|
|
||||||
let label = uci.get(uciconfig, ucisection, 'label');
|
let label = uci.get(uciconfig, ucisection, 'label');
|
||||||
if (label) {
|
if (label) {
|
||||||
return label;
|
return label;
|
||||||
@@ -218,32 +439,127 @@ return baseclass.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadModalTitle(title, addtitle, uciconfig, ucisection) {
|
loadModalTitle(title, addtitle, uciconfig, ucisection) {
|
||||||
|
if (!ucisection)
|
||||||
|
return addtitle;
|
||||||
|
|
||||||
let label = uci.get(uciconfig, ucisection, 'label');
|
let label = uci.get(uciconfig, ucisection, 'label');
|
||||||
return label ? title + ' » ' + label : addtitle;
|
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) {
|
renderSectionAdd(section, extra_class) {
|
||||||
let el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]),
|
let el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]),
|
||||||
nameEl = el.querySelector('.cbi-section-create-name');
|
nameEl = el.querySelector('.cbi-section-create-name'),
|
||||||
ui.addValidator(nameEl, 'uciname', true, (v) => {
|
button = el.querySelector('.cbi-section-create > .cbi-button-add'),
|
||||||
let button = el.querySelector('.cbi-section-create > .cbi-button-add');
|
uciconfig = section.uciconfig || section.map.config,
|
||||||
let 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;
|
button.disabled = true;
|
||||||
return true;
|
return;
|
||||||
} 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 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;
|
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) {
|
uploadCertificate(_option, type, filename, ev) {
|
||||||
const callWriteCertificate = rpc.declare({
|
const callWriteCertificate = rpc.declare({
|
||||||
object: 'luci.homeproxy',
|
object: 'luci.homeproxy',
|
||||||
@@ -264,6 +580,44 @@ return baseclass.extend({
|
|||||||
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
|
.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) {
|
validateBase64Key(length, section_id, value) {
|
||||||
/* Thanks to luci-proto-wireguard */
|
/* Thanks to luci-proto-wireguard */
|
||||||
if (section_id && value)
|
if (section_id && value)
|
||||||
@@ -304,7 +658,7 @@ return baseclass.extend({
|
|||||||
if (section_id) {
|
if (section_id) {
|
||||||
if (!value)
|
if (!value)
|
||||||
return _('Expecting: %s').format(_('non-empty value'));
|
return _('Expecting: %s').format(_('non-empty value'));
|
||||||
if (ucioption === 'node' && value === 'urltest')
|
if (ucioption === 'node' && (value === 'urltest' || value === 'selector'))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
let duplicate = false;
|
let duplicate = false;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -7,6 +7,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
'require form';
|
'require form';
|
||||||
'require fs';
|
'require fs';
|
||||||
|
'require rpc';
|
||||||
'require uci';
|
'require uci';
|
||||||
'require ui';
|
'require ui';
|
||||||
'require view';
|
'require view';
|
||||||
@@ -14,11 +15,207 @@
|
|||||||
'require homeproxy as hp';
|
'require homeproxy as hp';
|
||||||
'require tools.widgets as widgets';
|
'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) {
|
function allowInsecureConfirm(ev, _section_id, value) {
|
||||||
if (value === '1' && !confirm(_('Are you sure to allow insecure?')))
|
if (value === '1' && !confirm(_('Are you sure to allow insecure?')))
|
||||||
ev.target.firstElementChild.checked = null;
|
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) {
|
function parseShareLink(uri, features) {
|
||||||
let config, url, params;
|
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 */
|
/* https://github.com/anytls/anytls-go/blob/v0.0.8/docs/uri_scheme.md */
|
||||||
url = new URL('http://' + uri[1]);
|
url = new URL('http://' + uri[1]);
|
||||||
params = url.searchParams;
|
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 */
|
/* Check if password exists */
|
||||||
if (!url.username)
|
if (!url.username)
|
||||||
@@ -42,7 +244,8 @@ function parseShareLink(uri, features) {
|
|||||||
password: url.username ? decodeURIComponent(url.username) : null,
|
password: url.username ? decodeURIComponent(url.username) : null,
|
||||||
tls: '1',
|
tls: '1',
|
||||||
tls_sni: params.get('sni'),
|
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;
|
break;
|
||||||
@@ -75,6 +278,7 @@ function parseShareLink(uri, features) {
|
|||||||
type: 'hysteria',
|
type: 'hysteria',
|
||||||
address: url.hostname,
|
address: url.hostname,
|
||||||
port: url.port || '80',
|
port: url.port || '80',
|
||||||
|
hysteria_hopping_port: normalizeHysteriaHoppingPort(params.get('mport')),
|
||||||
hysteria_protocol: params.get('protocol') || 'udp',
|
hysteria_protocol: params.get('protocol') || 'udp',
|
||||||
hysteria_auth_type: params.get('auth') ? 'string' : null,
|
hysteria_auth_type: params.get('auth') ? 'string' : null,
|
||||||
hysteria_auth_payload: params.get('auth'),
|
hysteria_auth_payload: params.get('auth'),
|
||||||
@@ -82,9 +286,9 @@ function parseShareLink(uri, features) {
|
|||||||
hysteria_down_mbps: params.get('downmbps'),
|
hysteria_down_mbps: params.get('downmbps'),
|
||||||
hysteria_up_mbps: params.get('upmbps'),
|
hysteria_up_mbps: params.get('upmbps'),
|
||||||
tls: '1',
|
tls: '1',
|
||||||
|
tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0',
|
||||||
tls_sni: params.get('peer'),
|
tls_sni: params.get('peer'),
|
||||||
tls_alpn: params.get('alpn'),
|
tls_alpn: params.get('alpn')
|
||||||
tls_insecure: (params.get('insecure') === '1') ? '1' : '0'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -105,11 +309,12 @@ function parseShareLink(uri, features) {
|
|||||||
password: url.username ? (
|
password: url.username ? (
|
||||||
decodeURIComponent(url.username + (url.password ? (':' + url.password) : ''))
|
decodeURIComponent(url.username + (url.password ? (':' + url.password) : ''))
|
||||||
) : null,
|
) : null,
|
||||||
|
hysteria_hopping_port: normalizeHysteriaHoppingPort(params.get('mport')),
|
||||||
hysteria_obfs_type: params.get('obfs'),
|
hysteria_obfs_type: params.get('obfs'),
|
||||||
hysteria_obfs_password: params.get('obfs-password'),
|
hysteria_obfs_password: params.get('obfs-password'),
|
||||||
tls: '1',
|
tls: '1',
|
||||||
tls_sni: params.get('sni'),
|
tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0',
|
||||||
tls_insecure: params.get('insecure') ? '1' : '0'
|
tls_sni: params.get('sni')
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -212,6 +417,7 @@ function parseShareLink(uri, features) {
|
|||||||
password: decodeURIComponent(url.username),
|
password: decodeURIComponent(url.username),
|
||||||
transport: params.get('type') !== 'tcp' ? params.get('type') : null,
|
transport: params.get('type') !== 'tcp' ? params.get('type') : null,
|
||||||
tls: '1',
|
tls: '1',
|
||||||
|
tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0',
|
||||||
tls_sni: params.get('sni')
|
tls_sni: params.get('sni')
|
||||||
};
|
};
|
||||||
switch (params.get('type')) {
|
switch (params.get('type')) {
|
||||||
@@ -249,6 +455,7 @@ function parseShareLink(uri, features) {
|
|||||||
tuic_congestion_control: params.get('congestion_control'),
|
tuic_congestion_control: params.get('congestion_control'),
|
||||||
tuic_udp_relay_mode: params.get('udp_relay_mode'),
|
tuic_udp_relay_mode: params.get('udp_relay_mode'),
|
||||||
tls: '1',
|
tls: '1',
|
||||||
|
tls_insecure: (params.get('insecure') === '1' || params.get('allowInsecure') === '1') ? '1' : '0',
|
||||||
tls_sni: params.get('sni'),
|
tls_sni: params.get('sni'),
|
||||||
tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null
|
tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null
|
||||||
};
|
};
|
||||||
@@ -276,6 +483,7 @@ function parseShareLink(uri, features) {
|
|||||||
uuid: url.username,
|
uuid: url.username,
|
||||||
transport: params.get('type') !== 'tcp' ? params.get('type') : null,
|
transport: params.get('type') !== 'tcp' ? params.get('type') : null,
|
||||||
tls: ['tls', 'xtls', 'reality'].includes(params.get('security')) ? '1' : '0',
|
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_sni: params.get('sni'),
|
||||||
tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null,
|
tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null,
|
||||||
tls_reality: (params.get('security') === 'reality') ? '1' : '0',
|
tls_reality: (params.get('security') === 'reality') ? '1' : '0',
|
||||||
@@ -388,13 +596,378 @@ function parseShareLink(uri, features) {
|
|||||||
return config;
|
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 '<strong style="%s">%s</strong>'.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;
|
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.rowcolors = true;
|
||||||
s.sortable = true;
|
s.sortable = true;
|
||||||
s.nodescriptions = true;
|
s.nodescriptions = true;
|
||||||
|
s.max_cols = 7;
|
||||||
s.modaltitle = L.bind(hp.loadModalTitle, this, _('Node'), _('Add a node'), data[0]);
|
s.modaltitle = L.bind(hp.loadModalTitle, this, _('Node'), _('Add a node'), data[0]);
|
||||||
s.sectiontitle = L.bind(hp.loadDefaultLabel, this, 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') {
|
if (routing_mode !== 'custom') {
|
||||||
o = s.option(form.Button, '_apply', _('Apply'));
|
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 = s.option(form.Value, 'label', _('Label'));
|
||||||
o.load = L.bind(hp.loadDefaultLabel, this, data[0]);
|
o.load = L.bind(hp.loadDefaultLabel, this, data[0]);
|
||||||
o.validate = L.bind(hp.validateUniqueValue, this, data[0], 'node', 'label');
|
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('wireguard', _('WireGuard'));
|
||||||
o.value('vless', _('VLESS'));
|
o.value('vless', _('VLESS'));
|
||||||
o.value('vmess', _('VMess'));
|
o.value('vmess', _('VMess'));
|
||||||
|
o.default = 'direct';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.option(form.Value, 'address', _('Address'));
|
o = s.option(form.Value, 'address', _('Address'));
|
||||||
@@ -493,15 +1102,6 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
|||||||
}
|
}
|
||||||
o.modalonly = true;
|
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 */
|
/* AnyTLS config start */
|
||||||
o = s.option(form.Value, 'anytls_idle_session_check_interval', _('Idle session check interval'),
|
o = s.option(form.Value, 'anytls_idle_session_check_interval', _('Idle session check interval'),
|
||||||
_('Interval checking for idle sessions, in seconds.'));
|
_('Interval checking for idle sessions, in seconds.'));
|
||||||
@@ -746,7 +1346,7 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) {
|
|||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.option(form.ListValue, 'vmess_encrypt', _('Encrypt method'));
|
o = s.option(form.ListValue, 'vmess_encrypt', _('Encrypt method'));
|
||||||
o.value('auto');
|
o.value('auto', _('Default'));
|
||||||
o.value('none');
|
o.value('none');
|
||||||
o.value('zero');
|
o.value('zero');
|
||||||
o.value('aes-128-gcm');
|
o.value('aes-128-gcm');
|
||||||
@@ -1194,19 +1794,18 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render(data) {
|
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 main_node = uci.get(data[0], 'config', 'main_node');
|
||||||
let routing_mode = uci.get(data[0], 'config', 'routing_mode');
|
let routing_mode = uci.get(data[0], 'config', 'routing_mode');
|
||||||
let features = data[1];
|
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 */
|
/* Cache subscription information, it will be called multiple times */
|
||||||
let subinfo = [];
|
let subinfo = getSubscriptionInfo(data[0]);
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
m = new form.Map('homeproxy', _('Edit nodes'));
|
m = new form.Map('homeproxy', _('Edit nodes'));
|
||||||
|
|
||||||
@@ -1216,7 +1815,7 @@ return view.extend({
|
|||||||
/* User nodes start */
|
/* User nodes start */
|
||||||
s.tab('node', _('Nodes'));
|
s.tab('node', _('Nodes'));
|
||||||
o = s.taboption('node', form.SectionValue, '_node', form.GridSection, 'node');
|
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.addremove = true;
|
||||||
ss.filter = function(section_id) {
|
ss.filter = function(section_id) {
|
||||||
for (let info of subinfo)
|
for (let info of subinfo)
|
||||||
@@ -1287,24 +1886,7 @@ return view.extend({
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
ss.renderSectionAdd = function(/* ... */) {
|
ss.renderSectionAdd = function(/* ... */) {
|
||||||
let el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments),
|
let el = hp.renderSectionAdd(this, arguments[0]);
|
||||||
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');
|
|
||||||
|
|
||||||
el.appendChild(E('button', {
|
el.appendChild(E('button', {
|
||||||
'class': 'cbi-button cbi-button-add',
|
'class': 'cbi-button cbi-button-add',
|
||||||
@@ -1319,9 +1901,9 @@ return view.extend({
|
|||||||
|
|
||||||
/* Subscription nodes start */
|
/* Subscription nodes start */
|
||||||
for (const info of subinfo) {
|
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');
|
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) {
|
ss.filter = function(section_id) {
|
||||||
return (uci.get(data[0], section_id, 'grouphash') === info.hash);
|
return (uci.get(data[0], section_id, 'grouphash') === info.hash);
|
||||||
}
|
}
|
||||||
@@ -1336,32 +1918,35 @@ return view.extend({
|
|||||||
_('Auto update subscriptions and geodata.'));
|
_('Auto update subscriptions and geodata.'));
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.taboption('subscription', form.ListValue, 'auto_update_time', _('Update time'));
|
o = s.taboption('subscription', form.Value, 'auto_update_time', _('Update time (weekly)'));
|
||||||
for (let i = 0; i < 24; i++)
|
o.renderWidget = function() {
|
||||||
o.value(i, i + ':00');
|
return hp.renderCronSelector.apply(this, arguments);
|
||||||
o.default = '2';
|
};
|
||||||
o.depends('auto_update', '1');
|
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'),
|
o = s.taboption('subscription', form.Flag, 'update_via_proxy', _('Update via proxy'),
|
||||||
_('Update subscriptions via proxy.'));
|
_('Update subscriptions via proxy.'));
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.taboption('subscription', form.DynamicList, 'subscription_url', _('Subscription URL-s'),
|
o = s.taboption('subscription', form.ListValue, 'latency_test_mode', _('Latency test mode'),
|
||||||
_('Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.'));
|
_('Choose which method node latency tests use.'));
|
||||||
o.validate = function(section_id, value) {
|
o.value('icmp', _('ICMP'));
|
||||||
if (section_id && value) {
|
o.value('rp', _('RP'));
|
||||||
try {
|
o.default = 'icmp';
|
||||||
let url = new URL(value);
|
o.rmempty = false;
|
||||||
if (!url.hostname)
|
|
||||||
return _('Expecting: %s').format(_('valid URL'));
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
return _('Expecting: %s').format(_('valid URL'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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'),
|
o = s.taboption('subscription', form.ListValue, 'filter_nodes', _('Filter nodes'),
|
||||||
_('Drop/keep specific nodes from subscriptions.'));
|
_('Drop/keep specific nodes from subscriptions.'));
|
||||||
@@ -1396,8 +1981,16 @@ return view.extend({
|
|||||||
o.inputstyle = 'apply';
|
o.inputstyle = 'apply';
|
||||||
o.inputtitle = _('Save current settings');
|
o.inputtitle = _('Save current settings');
|
||||||
o.onclick = function() {
|
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(() => {
|
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 */
|
/* 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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -126,6 +126,8 @@ return view.extend({
|
|||||||
let m, s, o;
|
let m, s, o;
|
||||||
let features = data[1];
|
let features = data[1];
|
||||||
|
|
||||||
|
hp.installCloseButtonText();
|
||||||
|
|
||||||
m = new form.Map('homeproxy', _('HomeProxy Server'),
|
m = new form.Map('homeproxy', _('HomeProxy Server'),
|
||||||
_('The modern ImmortalWrt proxy platform for ARM64/AMD64.'));
|
_('The modern ImmortalWrt proxy platform for ARM64/AMD64.'));
|
||||||
|
|
||||||
@@ -188,6 +190,7 @@ return view.extend({
|
|||||||
o.value('tuic', _('Tuic'));
|
o.value('tuic', _('Tuic'));
|
||||||
o.value('vless', _('VLESS'));
|
o.value('vless', _('VLESS'));
|
||||||
o.value('vmess', _('VMess'));
|
o.value('vmess', _('VMess'));
|
||||||
|
o.default = 'mixed';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.option(form.Value, 'address', _('Listen address'));
|
o = s.option(form.Value, 'address', _('Listen address'));
|
||||||
@@ -637,10 +640,10 @@ return view.extend({
|
|||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
o = s.option(form.ListValue, 'tls_dns01_provider', _('DNS provider'));
|
o = s.option(form.ListValue, 'tls_dns01_provider', _('DNS provider'));
|
||||||
o.value('alidns', _('Alibaba Cloud DNS'));
|
o.value('alidns', _('AliYun'));
|
||||||
o.value('cloudflare', _('Cloudflare'));
|
o.value('cfdns', _('CloudFlare'));
|
||||||
o.depends('tls_dns01_challenge', '1');
|
o.depends('tls_dns01_challenge', '1');
|
||||||
o.default = 'cloudflare';
|
o.default = 'cfdns';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
@@ -663,7 +666,7 @@ return view.extend({
|
|||||||
|
|
||||||
o = s.option(form.Value, 'tls_dns01_cf_api_token', _('API token'));
|
o = s.option(form.Value, 'tls_dns01_cf_api_token', _('API token'));
|
||||||
o.password = true;
|
o.password = true;
|
||||||
o.depends('tls_dns01_provider', 'cloudflare');
|
o.depends('tls_dns01_provider', 'cfdns');
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
o.modalonly = true;
|
o.modalonly = true;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
'require ui';
|
'require ui';
|
||||||
'require view';
|
'require view';
|
||||||
|
|
||||||
|
'require homeproxy as hp';
|
||||||
|
|
||||||
/* Thanks to luci-app-aria2 */
|
/* Thanks to luci-app-aria2 */
|
||||||
const css = ' \
|
const css = ' \
|
||||||
#log_textarea { \
|
#log_textarea { \
|
||||||
@@ -27,6 +29,53 @@ const css = ' \
|
|||||||
} \
|
} \
|
||||||
.description { \
|
.description { \
|
||||||
background-color: #33ccff; \
|
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';
|
const hp_dir = '/var/run/homeproxy';
|
||||||
@@ -44,13 +93,13 @@ function getConnStat(o, site) {
|
|||||||
'class': 'btn cbi-button cbi-button-action',
|
'class': 'btn cbi-button cbi-button-action',
|
||||||
'click': ui.createHandlerFn(this, () => {
|
'click': ui.createHandlerFn(this, () => {
|
||||||
return L.resolveDefault(callConnStat(site), {}).then((ret) => {
|
return L.resolveDefault(callConnStat(site), {}).then((ret) => {
|
||||||
let ele = o.default.firstElementChild.nextElementSibling;
|
let ele = o.default.firstElementChild.nextElementSibling;
|
||||||
if (ret.result) {
|
if (ret.result) {
|
||||||
ele.style.setProperty('color', 'green');
|
ele.style.setProperty('color', 'green');
|
||||||
ele.innerHTML = _('passed');
|
ele.innerHTML = _('passed');
|
||||||
} else {
|
} else {
|
||||||
ele.style.setProperty('color', 'red');
|
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) {
|
function getResVersion(o, type) {
|
||||||
const callResVersion = rpc.declare({
|
const callResVersion = rpc.declare({
|
||||||
object: 'luci.homeproxy',
|
object: 'luci.homeproxy',
|
||||||
@@ -227,16 +323,16 @@ return view.extend({
|
|||||||
render() {
|
render() {
|
||||||
let m, s, o;
|
let m, s, o;
|
||||||
|
|
||||||
|
hp.installCloseButtonText();
|
||||||
|
|
||||||
m = new form.Map('homeproxy');
|
m = new form.Map('homeproxy');
|
||||||
|
|
||||||
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check'));
|
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check'));
|
||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
|
|
||||||
o = s.option(form.DummyValue, '_check_baidu', _('BaiDu'));
|
o = s.option(form.DummyValue, '_connection_check');
|
||||||
o.cfgvalue = L.bind(getConnStat, this, o, 'baidu');
|
o.rawhtml = true;
|
||||||
|
o.render = renderConnCards;
|
||||||
o = s.option(form.DummyValue, '_check_google', _('Google'));
|
|
||||||
o.cfgvalue = L.bind(getConnStat, this, o, 'google');
|
|
||||||
|
|
||||||
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
|
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
|
||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -25,8 +25,8 @@ config homeproxy 'migration'
|
|||||||
config homeproxy 'config'
|
config homeproxy 'config'
|
||||||
option main_node 'nil'
|
option main_node 'nil'
|
||||||
option main_udp_node 'same'
|
option main_udp_node 'same'
|
||||||
option dns_server '8.8.8.8'
|
option dns_server 'https://dns.quad9.net/dns-query'
|
||||||
option china_dns_server '223.5.5.5'
|
option china_dns_server 'https://dns.alidns.com/dns-query'
|
||||||
option routing_mode 'bypass_mainland_china'
|
option routing_mode 'bypass_mainland_china'
|
||||||
option routing_port 'common'
|
option routing_port 'common'
|
||||||
option proxy_mode 'redirect_tproxy'
|
option proxy_mode 'redirect_tproxy'
|
||||||
@@ -38,20 +38,36 @@ config homeproxy 'control'
|
|||||||
option lan_proxy_mode 'disabled'
|
option lan_proxy_mode 'disabled'
|
||||||
list wan_proxy_ipv4_ips '91.105.192.0/23'
|
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.4.0/22'
|
||||||
list wan_proxy_ipv4_ips '91.108.8.0/22'
|
list wan_proxy_ipv4_ips '91.108.8.0/21'
|
||||||
list wan_proxy_ipv4_ips '91.108.16.0/22'
|
list wan_proxy_ipv4_ips '91.108.16.0/21'
|
||||||
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.56.0/22'
|
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 '149.154.160.0/20'
|
||||||
list wan_proxy_ipv4_ips '185.76.151.0/24'
|
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:67c:4e8::/48'
|
||||||
list wan_proxy_ipv6_ips '2001:b28:f23c::/48'
|
list wan_proxy_ipv6_ips '2001:b28:f23c::/47'
|
||||||
list wan_proxy_ipv6_ips '2001:b28:f23d::/48'
|
|
||||||
list wan_proxy_ipv6_ips '2001:b28:f23f::/48'
|
list wan_proxy_ipv6_ips '2001:b28:f23f::/48'
|
||||||
list wan_proxy_ipv6_ips '2a0a:f280::/32'
|
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'
|
config homeproxy 'routing'
|
||||||
option sniff_override '1'
|
option sniff_override '1'
|
||||||
option default_outbound 'direct-out'
|
option default_outbound 'direct-out'
|
||||||
@@ -66,13 +82,12 @@ config homeproxy 'dns'
|
|||||||
config homeproxy 'subscription'
|
config homeproxy 'subscription'
|
||||||
option auto_update '0'
|
option auto_update '0'
|
||||||
option allow_insecure '0'
|
option allow_insecure '0'
|
||||||
|
option latency_test_mode 'icmp'
|
||||||
option packet_encoding 'xudp'
|
option packet_encoding 'xudp'
|
||||||
option update_via_proxy '0'
|
option update_via_proxy '0'
|
||||||
option filter_nodes 'blacklist'
|
option auto_update_time '0 0 * * *'
|
||||||
list filter_keywords '重置|到期|过期|剩余|套餐'
|
option filter_nodes 'disabled'
|
||||||
list filter_keywords 'Expiration|Remaining'
|
|
||||||
|
|
||||||
config homeproxy 'server'
|
config homeproxy 'server'
|
||||||
option enabled '0'
|
option enabled '0'
|
||||||
option log_level 'warn'
|
option log_level 'warn'
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1 +1 @@
|
|||||||
20260704060757
|
202606132337
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
2001:250::/30
|
2001:250::/30
|
||||||
2001:254::/31
|
2001:254::/33
|
||||||
|
2001:255::/32
|
||||||
2001:256:100::/48
|
2001:256:100::/48
|
||||||
|
2001:678:53c::/48
|
||||||
2001:c68::/32
|
2001:c68::/32
|
||||||
2001:cc0::/32
|
2001:cc0::/32
|
||||||
2001:da8::/32
|
2001:da8::/32
|
||||||
@@ -11,8 +13,11 @@
|
|||||||
2001:dc7::/32
|
2001:dc7::/32
|
||||||
2001:dd8:1::/48
|
2001:dd8:1::/48
|
||||||
2001:dd9::/48
|
2001:dd9::/48
|
||||||
|
2001:df5:4740::/48
|
||||||
2001:df6:40::/48
|
2001:df6:40::/48
|
||||||
2001:4510:400::/40
|
2001:4510:400::/40
|
||||||
|
2001:4510:1480::/41
|
||||||
|
2001:4511:1480::/41
|
||||||
2400:1160::/32
|
2400:1160::/32
|
||||||
2400:3200::/32
|
2400:3200::/32
|
||||||
2400:5280:f803::/48
|
2400:5280:f803::/48
|
||||||
@@ -20,6 +25,8 @@
|
|||||||
2400:6000::/32
|
2400:6000::/32
|
||||||
2400:6600::/32
|
2400:6600::/32
|
||||||
2400:6e60:1301::/48
|
2400:6e60:1301::/48
|
||||||
|
2400:7100::/32
|
||||||
|
2400:73e0::/32
|
||||||
2400:75aa::/32
|
2400:75aa::/32
|
||||||
2400:7fc0::/40
|
2400:7fc0::/40
|
||||||
2400:7fc0:220::/44
|
2400:7fc0:220::/44
|
||||||
@@ -36,9 +43,14 @@
|
|||||||
2400:8200::/32
|
2400:8200::/32
|
||||||
2400:89c0:1010::/44
|
2400:89c0:1010::/44
|
||||||
2400:89c0:1050::/46
|
2400:89c0:1050::/46
|
||||||
|
2400:89c0:1130::/44
|
||||||
2400:89c0:1150::/48
|
2400:89c0:1150::/48
|
||||||
|
2400:89c0:2100::/48
|
||||||
|
2400:89c0:2200::/48
|
||||||
|
2400:89c0:3010::/44
|
||||||
2400:89c0:6000::/48
|
2400:89c0:6000::/48
|
||||||
2400:8fc0::/32
|
2400:89c0:6100::/48
|
||||||
|
2400:8fc0:571::/48
|
||||||
2400:9020:f010::/46
|
2400:9020:f010::/46
|
||||||
2400:9380:8001::/48
|
2400:9380:8001::/48
|
||||||
2400:9380:8003::/48
|
2400:9380:8003::/48
|
||||||
@@ -85,15 +97,19 @@
|
|||||||
2400:cb80:e40::/44
|
2400:cb80:e40::/44
|
||||||
2400:da00::/32
|
2400:da00::/32
|
||||||
2400:dd00::/28
|
2400:dd00::/28
|
||||||
2400:ee00:ffec::/46
|
2400:e680::/32
|
||||||
2400:ee00:fff0::/44
|
2400:ee00::/32
|
||||||
2400:f6e0::/32
|
2400:f6e0::/32
|
||||||
2400:f720::/32
|
2400:f720::/32
|
||||||
2400:fe00::/32
|
2400:fe00::/32
|
||||||
|
2401:20::/40
|
||||||
|
2401:20:1330::/46
|
||||||
2401:800::/32
|
2401:800::/32
|
||||||
2401:1160::/32
|
2401:1160::/32
|
||||||
|
2401:1200::/48
|
||||||
2401:1320::/32
|
2401:1320::/32
|
||||||
2401:1d40::/32
|
2401:1d40::/32
|
||||||
|
2401:2780::/32
|
||||||
2401:2a00:f000::/43
|
2401:2a00:f000::/43
|
||||||
2401:2e00::/32
|
2401:2e00::/32
|
||||||
2401:3480::/36
|
2401:3480::/36
|
||||||
@@ -101,11 +117,39 @@
|
|||||||
2401:3480:3000::/36
|
2401:3480:3000::/36
|
||||||
2401:34a0::/31
|
2401:34a0::/31
|
||||||
2401:3800::/32
|
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:5c20:10::/48
|
||||||
2401:71c0::/48
|
2401:71c0::/48
|
||||||
2401:7660::/48
|
2401:7660::/48
|
||||||
|
2401:7700::/32
|
||||||
|
2401:7d40::/32
|
||||||
2401:7e00::/32
|
2401:7e00::/32
|
||||||
2401:8be0::/48
|
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::/44
|
||||||
2401:9a00:10::/46
|
2401:9a00:10::/46
|
||||||
2401:a140:1::/48
|
2401:a140:1::/48
|
||||||
@@ -121,26 +165,56 @@
|
|||||||
2401:c020:6::/48
|
2401:c020:6::/48
|
||||||
2401:c020:8::/47
|
2401:c020:8::/47
|
||||||
2401:c020:14::/48
|
2401:c020:14::/48
|
||||||
|
2401:ca00::/32
|
||||||
2401:cb80::/32
|
2401:cb80::/32
|
||||||
2401:cc00::/32
|
2401:cc00::/32
|
||||||
2401:ce00::/32
|
2401:ce00::/32
|
||||||
2401:d0e0:3::/48
|
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:d920::/48
|
||||||
2401:de00::/32
|
2401:de00::/32
|
||||||
2401:ec00::/32
|
2401:ec00::/32
|
||||||
2401:fa00:40::/48
|
2401:fa00:40::/48
|
||||||
2402:840:d000::/46
|
2402:840:d000::/46
|
||||||
2402:840:e000::/46
|
2402:840:e000::/46
|
||||||
|
2402:840:f000::/38
|
||||||
2402:c60::/48
|
2402:c60::/48
|
||||||
2402:1440::/32
|
2402:1440::/32
|
||||||
|
2402:18a0::/32
|
||||||
2402:2000::/32
|
2402:2000::/32
|
||||||
|
2402:2e60::/39
|
||||||
|
2402:34e0::/32
|
||||||
2402:36e0::/44
|
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:4b80::/32
|
||||||
2402:4e00::/32
|
2402:4e00::/32
|
||||||
|
2402:5e40::/32
|
||||||
2402:5ec0::/32
|
2402:5ec0::/32
|
||||||
2402:6e80::/32
|
2402:6e80::/32
|
||||||
2402:6f40::/48
|
2402:6f40::/48
|
||||||
2402:6f40:2::/47
|
2402:6f40:2::/47
|
||||||
|
2402:6fc0::/48
|
||||||
|
2402:7040::/32
|
||||||
2402:7d80::/48
|
2402:7d80::/48
|
||||||
2402:7d80:240::/48
|
2402:7d80:240::/48
|
||||||
2402:7d80:8888::/48
|
2402:7d80:8888::/48
|
||||||
@@ -162,7 +236,12 @@
|
|||||||
2402:dfc0:50::/44
|
2402:dfc0:50::/44
|
||||||
2402:e480::/32
|
2402:e480::/32
|
||||||
2402:e740::/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:e880::/48
|
||||||
2402:ef40::/32
|
2402:ef40::/32
|
||||||
2402:f000::/32
|
2402:f000::/32
|
||||||
@@ -190,6 +269,7 @@
|
|||||||
2403:7580::/32
|
2403:7580::/32
|
||||||
2403:8080:101::/48
|
2403:8080:101::/48
|
||||||
2403:8c00::/32
|
2403:8c00::/32
|
||||||
|
2403:9b00:2000::/48
|
||||||
2403:9b00:2400::/48
|
2403:9b00:2400::/48
|
||||||
2403:9b00:6000::/48
|
2403:9b00:6000::/48
|
||||||
2403:a100::/48
|
2403:a100::/48
|
||||||
@@ -251,6 +331,10 @@
|
|||||||
2404:c940::/48
|
2404:c940::/48
|
||||||
2404:e280::/47
|
2404:e280::/47
|
||||||
2404:e8c0::/32
|
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:10::/48
|
||||||
2405:80:13::/48
|
2405:80:13::/48
|
||||||
2405:6c0:2::/48
|
2405:6c0:2::/48
|
||||||
@@ -270,24 +354,130 @@
|
|||||||
2405:6f00:c602::/48
|
2405:6f00:c602::/48
|
||||||
2405:7040:6000::/47
|
2405:7040:6000::/47
|
||||||
2405:78c0:6e00::/43
|
2405:78c0:6e00::/43
|
||||||
|
2405:8280::/32
|
||||||
2405:8a40::/32
|
2405:8a40::/32
|
||||||
2405:a900:ffee::/48
|
2405:a900:ffee::/48
|
||||||
|
2405:a900:fffe::/48
|
||||||
2405:ad00::/32
|
2405:ad00::/32
|
||||||
2405:b7c0::/32
|
2405:b7c0::/32
|
||||||
2405:d900::/32
|
2405:d900::/32
|
||||||
2405:e000::/32
|
2405:e000::/32
|
||||||
|
2405:e600:2::/48
|
||||||
2405:f940::/32
|
2405:f940::/32
|
||||||
2406:280::/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: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:1080::/48
|
||||||
|
2406:1e40:f012::/47
|
||||||
2406:2700::/32
|
2406:2700::/32
|
||||||
2406:3340::/32
|
2406:3340::/32
|
||||||
|
2406:3640:1::/48
|
||||||
2406:4d00::/48
|
2406:4d00::/48
|
||||||
2406:52c0::/32
|
2406:52c0::/32
|
||||||
2406:5340:6666::/48
|
2406:5340:6666::/48
|
||||||
2406:5340:8888::/48
|
2406:5340:8888::/48
|
||||||
2406:6100::/32
|
2406:6100::/32
|
||||||
|
2406:8880::/48
|
||||||
2406:b640:100::/48
|
2406:b640:100::/48
|
||||||
2406:b640:4100::/48
|
2406:b640:4100::/48
|
||||||
2406:cac0::/40
|
2406:cac0::/40
|
||||||
@@ -304,6 +494,7 @@
|
|||||||
2407:23c0::/48
|
2407:23c0::/48
|
||||||
2407:2840::/48
|
2407:2840::/48
|
||||||
2407:37c0::/32
|
2407:37c0::/32
|
||||||
|
2407:4980::/32
|
||||||
2407:5380::/32
|
2407:5380::/32
|
||||||
2407:6c40:1100::/48
|
2407:6c40:1100::/48
|
||||||
2407:6c40:1210::/48
|
2407:6c40:1210::/48
|
||||||
@@ -322,7 +513,51 @@
|
|||||||
2407:c080:6000::/36
|
2407:c080:6000::/36
|
||||||
2407:c080:8000::/36
|
2407:c080:8000::/36
|
||||||
2407:d9c0::/32
|
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::/48
|
||||||
2408:8000:2::/47
|
2408:8000:2::/47
|
||||||
2408:8000:1000::/36
|
2408:8000:1000::/36
|
||||||
@@ -579,103 +814,86 @@
|
|||||||
2408:840c:dd00::/40
|
2408:840c:dd00::/40
|
||||||
2408:840c:de00::/40
|
2408:840c:de00::/40
|
||||||
2408:840d::/42
|
2408:840d::/42
|
||||||
2408:840d:40::/43
|
|
||||||
2408:840d:200::/42
|
2408:840d:200::/42
|
||||||
2408:840d:400::/42
|
2408:840d:400::/42
|
||||||
2408:840d:440::/43
|
2408:840d:440::/43
|
||||||
2408:840d:600::/42
|
2408:840d:600::/42
|
||||||
2408:840d:d00::/42
|
2408:840d:d00::/42
|
||||||
2408:840d:d40::/43
|
|
||||||
2408:840d:f00::/42
|
2408:840d:f00::/42
|
||||||
2408:840d:1100::/42
|
2408:840d:1100::/42
|
||||||
2408:840d:1140::/43
|
2408:840d:1140::/43
|
||||||
2408:840d:1300::/42
|
2408:840d:1300::/42
|
||||||
2408:840d:1a00::/42
|
2408:840d:1a00::/42
|
||||||
2408:840d:1a40::/43
|
|
||||||
2408:840d:1c00::/42
|
2408:840d:1c00::/42
|
||||||
2408:840d:1e00::/42
|
2408:840d:1e00::/42
|
||||||
2408:840d:1e40::/43
|
2408:840d:1e40::/43
|
||||||
2408:840d:2000::/42
|
2408:840d:2000::/42
|
||||||
2408:840d:2700::/42
|
2408:840d:2700::/42
|
||||||
2408:840d:2740::/43
|
|
||||||
2408:840d:2900::/42
|
2408:840d:2900::/42
|
||||||
2408:840d:2b00::/42
|
2408:840d:2b00::/42
|
||||||
2408:840d:2b40::/43
|
2408:840d:2b40::/43
|
||||||
2408:840d:2d00::/42
|
2408:840d:2d00::/42
|
||||||
2408:840d:3400::/42
|
2408:840d:3400::/42
|
||||||
2408:840d:3440::/43
|
|
||||||
2408:840d:3600::/42
|
2408:840d:3600::/42
|
||||||
2408:840d:3800::/42
|
2408:840d:3800::/42
|
||||||
2408:840d:3840::/43
|
2408:840d:3840::/43
|
||||||
2408:840d:3a00::/42
|
2408:840d:3a00::/42
|
||||||
2408:840d:4e00::/42
|
2408:840d:4e00::/42
|
||||||
2408:840d:4e40::/43
|
|
||||||
2408:840d:5000::/42
|
2408:840d:5000::/42
|
||||||
2408:840d:5200::/42
|
2408:840d:5200::/42
|
||||||
2408:840d:5240::/43
|
2408:840d:5240::/43
|
||||||
2408:840d:5400::/42
|
2408:840d:5400::/42
|
||||||
2408:840d:5b00::/42
|
2408:840d:5b00::/42
|
||||||
2408:840d:5b40::/43
|
|
||||||
2408:840d:5d00::/42
|
2408:840d:5d00::/42
|
||||||
2408:840d:5f00::/42
|
2408:840d:5f00::/42
|
||||||
2408:840d:5f40::/43
|
2408:840d:5f40::/43
|
||||||
2408:840d:6100::/42
|
2408:840d:6100::/42
|
||||||
2408:840d:6800::/42
|
2408:840d:6800::/42
|
||||||
2408:840d:6840::/43
|
|
||||||
2408:840d:6a00::/42
|
2408:840d:6a00::/42
|
||||||
2408:840d:6c00::/42
|
2408:840d:6c00::/42
|
||||||
2408:840d:6c40::/43
|
2408:840d:6c40::/43
|
||||||
2408:840d:6e00::/42
|
2408:840d:6e00::/42
|
||||||
2408:840d:7500::/42
|
2408:840d:7500::/42
|
||||||
2408:840d:7540::/43
|
|
||||||
2408:840d:7700::/42
|
2408:840d:7700::/42
|
||||||
2408:840d:7900::/42
|
2408:840d:7900::/42
|
||||||
2408:840d:7940::/43
|
2408:840d:7940::/43
|
||||||
2408:840d:7b00::/42
|
2408:840d:7b00::/42
|
||||||
2408:840d:8200::/42
|
2408:840d:8200::/42
|
||||||
2408:840d:8240::/43
|
|
||||||
2408:840d:8400::/42
|
2408:840d:8400::/42
|
||||||
2408:840d:8600::/42
|
2408:840d:8600::/42
|
||||||
2408:840d:8640::/43
|
2408:840d:8640::/43
|
||||||
2408:840d:8800::/42
|
2408:840d:8800::/42
|
||||||
2408:840d:8f00::/42
|
2408:840d:8f00::/42
|
||||||
2408:840d:8f40::/43
|
|
||||||
2408:840d:9100::/42
|
2408:840d:9100::/42
|
||||||
2408:840d:9300::/42
|
2408:840d:9300::/42
|
||||||
2408:840d:9340::/43
|
2408:840d:9340::/43
|
||||||
2408:840d:9500::/42
|
2408:840d:9500::/42
|
||||||
2408:840d:9c00::/42
|
2408:840d:9c00::/42
|
||||||
2408:840d:9c40::/43
|
|
||||||
2408:840d:9e00::/42
|
2408:840d:9e00::/42
|
||||||
2408:840d:a000::/42
|
2408:840d:a000::/42
|
||||||
2408:840d:a040::/43
|
2408:840d:a040::/43
|
||||||
2408:840d:a200::/42
|
2408:840d:a200::/42
|
||||||
2408:840d:a900::/42
|
2408:840d:a900::/42
|
||||||
2408:840d:a940::/43
|
|
||||||
2408:840d:ab00::/42
|
2408:840d:ab00::/42
|
||||||
2408:840d:ad00::/42
|
2408:840d:ad00::/42
|
||||||
2408:840d:ad40::/43
|
2408:840d:ad40::/43
|
||||||
2408:840d:af00::/42
|
2408:840d:af00::/42
|
||||||
2408:840d:b600::/42
|
2408:840d:b600::/42
|
||||||
2408:840d:b640::/43
|
|
||||||
2408:840d:b800::/42
|
2408:840d:b800::/42
|
||||||
2408:840d:ba00::/42
|
2408:840d:ba00::/42
|
||||||
2408:840d:ba40::/43
|
2408:840d:ba40::/43
|
||||||
2408:840d:bc00::/42
|
2408:840d:bc00::/42
|
||||||
2408:840d:c300::/42
|
2408:840d:c300::/42
|
||||||
2408:840d:c340::/43
|
|
||||||
2408:840d:c500::/42
|
2408:840d:c500::/42
|
||||||
2408:840d:c700::/42
|
2408:840d:c700::/42
|
||||||
2408:840d:c740::/43
|
2408:840d:c740::/43
|
||||||
2408:840d:c900::/42
|
2408:840d:c900::/42
|
||||||
2408:840d:d000::/42
|
2408:840d:d000::/42
|
||||||
2408:840d:d040::/43
|
|
||||||
2408:840d:d200::/42
|
2408:840d:d200::/42
|
||||||
2408:840d:d400::/42
|
2408:840d:d400::/42
|
||||||
2408:840d:d440::/43
|
2408:840d:d440::/43
|
||||||
2408:840d:d600::/42
|
2408:840d:d600::/42
|
||||||
2408:840d:dd00::/42
|
2408:840d:dd00::/42
|
||||||
2408:840d:dd40::/43
|
|
||||||
2408:840d:de00::/42
|
2408:840d:de00::/42
|
||||||
2408:840e:dd00::/40
|
2408:840e:dd00::/40
|
||||||
2408:840e:de00::/40
|
2408:840e:de00::/40
|
||||||
@@ -856,7 +1074,6 @@
|
|||||||
2408:8a06:100::/47
|
2408:8a06:100::/47
|
||||||
2408:8a07::/40
|
2408:8a07::/40
|
||||||
2408:8a21:4000::/35
|
2408:8a21:4000::/35
|
||||||
2408:8a22::/35
|
|
||||||
2408:8a23:4000::/34
|
2408:8a23:4000::/34
|
||||||
2408:8a24:4000::/34
|
2408:8a24:4000::/34
|
||||||
2408:8a26:c000::/34
|
2408:8a26:c000::/34
|
||||||
@@ -870,14 +1087,47 @@
|
|||||||
2409:27fc::/48
|
2409:27fc::/48
|
||||||
2409:27fe::/33
|
2409:27fe::/33
|
||||||
2409:6100::/44
|
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:2000::/40
|
||||||
240a:2001:100::/40
|
240a:2001:100::/40
|
||||||
240a:2001:1000::/36
|
240a:2001:1000::/36
|
||||||
|
240a:4002:1b00::/40
|
||||||
|
240a:4010:8000::/33
|
||||||
240a:4020:83a::/48
|
240a:4020:83a::/48
|
||||||
240a:4020:883a::/48
|
240a:4020:883a::/48
|
||||||
240a:4021:83a::/48
|
240a:4021:83a::/48
|
||||||
240a:4021:883a::/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:50::/48
|
||||||
240a:4090:120::/48
|
240a:4090:120::/48
|
||||||
240a:4090:250::/48
|
240a:4090:250::/48
|
||||||
@@ -892,11 +1142,20 @@
|
|||||||
240a:4090:5200::/40
|
240a:4090:5200::/40
|
||||||
240a:4090:7000::/39
|
240a:4090:7000::/39
|
||||||
240a:4090:7200::/40
|
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:83a::/48
|
||||||
240a:40b0:283a::/48
|
240a:40b0:283a::/48
|
||||||
240a:40b0:483a::/48
|
240a:40b0:483a::/48
|
||||||
240a:40b0:683a::/48
|
240a:40b0:683a::/48
|
||||||
240a:40c0:8000::/43
|
240a:40c0:8000::/43
|
||||||
|
240a:40c0:8200::/48
|
||||||
|
240a:40c0:8240::/48
|
||||||
240a:40c0:a000::/43
|
240a:40c0:a000::/43
|
||||||
240a:40c0:c000::/43
|
240a:40c0:c000::/43
|
||||||
240a:40c0:e000::/43
|
240a:40c0:e000::/43
|
||||||
@@ -923,10 +1182,31 @@
|
|||||||
240a:40c3:8000::/43
|
240a:40c3:8000::/43
|
||||||
240a:40c3:a000::/43
|
240a:40c3:a000::/43
|
||||||
240a:40c3:c000::/43
|
240a:40c3:c000::/43
|
||||||
|
240a:40c3:c200::/48
|
||||||
|
240a:40c3:c240::/48
|
||||||
240a:40c3:e000::/43
|
240a:40c3:e000::/43
|
||||||
240a:40c4::/43
|
240a:40c4::/43
|
||||||
240a:40c4:2000::/43
|
240a:40c4:2000::/43
|
||||||
240a:40c4:4000::/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:4280::/26
|
||||||
240a:42c0::/27
|
240a:42c0::/27
|
||||||
240a:42e0::/28
|
240a:42e0::/28
|
||||||
@@ -943,7 +1223,69 @@
|
|||||||
240c:4000::/22
|
240c:4000::/22
|
||||||
240c:c000::/20
|
240c:c000::/20
|
||||||
240d:4000::/21
|
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:8001::/48
|
||||||
2605:9d80:8011::/48
|
2605:9d80:8011::/48
|
||||||
2605:9d80:8021::/48
|
2605:9d80:8021::/48
|
||||||
@@ -959,6 +1301,8 @@
|
|||||||
2605:9d80:9092::/48
|
2605:9d80:9092::/48
|
||||||
2804:1e48:9001::/48
|
2804:1e48:9001::/48
|
||||||
2804:1e48:9002::/48
|
2804:1e48:9002::/48
|
||||||
|
2a01:e281:a400::/48
|
||||||
|
2a01:e281:a410::/44
|
||||||
2a04:3e00:1002::/48
|
2a04:3e00:1002::/48
|
||||||
2a04:f580:8010::/47
|
2a04:f580:8010::/47
|
||||||
2a04:f580:8090::/48
|
2a04:f580:8090::/48
|
||||||
@@ -983,26 +1327,163 @@
|
|||||||
2a04:f580:9270::/48
|
2a04:f580:9270::/48
|
||||||
2a04:f580:9280::/48
|
2a04:f580:9280::/48
|
||||||
2a04:f580:9290::/48
|
2a04:f580:9290::/48
|
||||||
2a0a:d685:1ff::/48
|
2a05:1087::/32
|
||||||
2a0a:d685:300::/40
|
2a06:3600:e000::/40
|
||||||
2a0b:4340:90::/48
|
2a06:3600:fb00::/40
|
||||||
2a0b:4340:97::/48
|
2a06:3600:fc00::/38
|
||||||
2a0d:c7c7:400::/38
|
2a06:3605::/32
|
||||||
2a0e:aa06:49f::/48
|
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:740::/44
|
||||||
2a0e:b107:2440::/44
|
2a0e:b107:da0::/44
|
||||||
2a0f:1cc5:1600::/44
|
2a0e:b107:14a0::/44
|
||||||
2a0f:1cc5:4700::/40
|
2a0f:1cc5:14::/46
|
||||||
2a12:cb41:1200::/44
|
2a0f:1cc5:1f::/48
|
||||||
2a12:cb41:1300::/43
|
2a0f:1cc5:40::/48
|
||||||
2a12:cb41:13f0::/44
|
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:1801:180::/43
|
||||||
2a13:a5c3:f000::/40
|
2a13:1801:200::/44
|
||||||
2a14:7580:d00::/43
|
2a13:8c87::/32
|
||||||
2a14:7580:d20::/46
|
2a13:a5c3:ff41::/48
|
||||||
2a14:7580:d41::/48
|
2a13:a5c3:ff50::/44
|
||||||
2a14:7580:d42::/47
|
2a14:67c1:70::/48
|
||||||
2a14:7580:dff::/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:8011::/48
|
||||||
2c0f:f7a8:8050::/48
|
2c0f:f7a8:8050::/48
|
||||||
2c0f:f7a8:805f::/48
|
2c0f:f7a8:805f::/48
|
||||||
@@ -1011,4 +1492,5 @@
|
|||||||
2c0f:f7a8:8211::/48
|
2c0f:f7a8:8211::/48
|
||||||
2c0f:f7a8:9020::/48
|
2c0f:f7a8:9020::/48
|
||||||
2c0f:f7a8:9041::/48
|
2c0f:f7a8:9041::/48
|
||||||
|
2c0f:f7a8:9211::/48
|
||||||
2c0f:f7a8:9220::/48
|
2c0f:f7a8:9220::/48
|
||||||
@@ -1 +1 @@
|
|||||||
20260704060757
|
202606132337
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1 +1 @@
|
|||||||
202607052256
|
202606132337
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
173ng.com
|
173ng.com
|
||||||
18board.com
|
18board.com
|
||||||
18comic.org
|
18comic.org
|
||||||
18mh.net
|
|
||||||
1984bbs.com
|
1984bbs.com
|
||||||
1991way.com
|
1991way.com
|
||||||
1e100.net
|
1e100.net
|
||||||
@@ -74,7 +73,6 @@
|
|||||||
9cache.com
|
9cache.com
|
||||||
9gag.com
|
9gag.com
|
||||||
9news.com.au
|
9news.com.au
|
||||||
9oju31.launches.appsflyersdk.com
|
|
||||||
a-normal-day.com
|
a-normal-day.com
|
||||||
a248.e.akamai.net
|
a248.e.akamai.net
|
||||||
aamacau.com
|
aamacau.com
|
||||||
@@ -89,7 +87,6 @@ about.gitlab.com
|
|||||||
about.me
|
about.me
|
||||||
abplive.com
|
abplive.com
|
||||||
acast.com
|
acast.com
|
||||||
accountboy.com
|
|
||||||
accountkit.com
|
accountkit.com
|
||||||
acevpn.com
|
acevpn.com
|
||||||
acg.mn
|
acg.mn
|
||||||
@@ -131,7 +128,6 @@ aiosearch.com
|
|||||||
aiph.net
|
aiph.net
|
||||||
airconsole.com
|
airconsole.com
|
||||||
airitilibrary.com
|
airitilibrary.com
|
||||||
airuniversity.af.edu
|
|
||||||
airvpn.org
|
airvpn.org
|
||||||
ait.org.tw
|
ait.org.tw
|
||||||
aiweiweiblog.com
|
aiweiweiblog.com
|
||||||
@@ -211,18 +207,13 @@ aoxvpn.com
|
|||||||
apartmentratings.com
|
apartmentratings.com
|
||||||
apartments.com
|
apartments.com
|
||||||
apat1989.org
|
apat1989.org
|
||||||
api-gl.lbkrs.com
|
|
||||||
api-secure.recaptcha.net
|
api-secure.recaptcha.net
|
||||||
api-verify.recaptcha.net
|
api-verify.recaptcha.net
|
||||||
api.ai
|
api.ai
|
||||||
api.futunn.com
|
|
||||||
api.lbkrs.com
|
|
||||||
api.moomoobull.com
|
|
||||||
api.palworldgame.com
|
api.palworldgame.com
|
||||||
api.pureapk.com
|
api.pureapk.com
|
||||||
api.recaptcha.net
|
api.recaptcha.net
|
||||||
api.steampowered.com
|
api.steampowered.com
|
||||||
api5.futunn.com
|
|
||||||
apiary.io
|
apiary.io
|
||||||
apigee.com
|
apigee.com
|
||||||
apk.support
|
apk.support
|
||||||
@@ -400,7 +391,6 @@ bigjapanesesex.com
|
|||||||
bignews.org
|
bignews.org
|
||||||
bigone.com
|
bigone.com
|
||||||
bild.de
|
bild.de
|
||||||
bilinovel.com
|
|
||||||
biliworld.com
|
biliworld.com
|
||||||
binance.com
|
binance.com
|
||||||
binance.org
|
binance.org
|
||||||
@@ -665,7 +655,6 @@ cdjp.org
|
|||||||
cdn-images.mailchimp.com
|
cdn-images.mailchimp.com
|
||||||
cdn-telegram.org
|
cdn-telegram.org
|
||||||
cdn.arstechnica.net
|
cdn.arstechnica.net
|
||||||
cdn.futustatic.com
|
|
||||||
cdn.jwplayer.com
|
cdn.jwplayer.com
|
||||||
cdn.printfriendly.com
|
cdn.printfriendly.com
|
||||||
cdn.seatguru.com
|
cdn.seatguru.com
|
||||||
@@ -689,7 +678,6 @@ centurys.net
|
|||||||
certificate-transparency.org
|
certificate-transparency.org
|
||||||
certificate.revocationcheck.com
|
certificate.revocationcheck.com
|
||||||
cfr.org
|
cfr.org
|
||||||
cg.play-analytics.com
|
|
||||||
cgdepot.org
|
cgdepot.org
|
||||||
change.org
|
change.org
|
||||||
changp.com
|
changp.com
|
||||||
@@ -871,7 +859,6 @@ crazypool.org
|
|||||||
crazyshit.com
|
crazyshit.com
|
||||||
crbug.com
|
crbug.com
|
||||||
crchina.org
|
crchina.org
|
||||||
creader.com
|
|
||||||
creaders.net
|
creaders.net
|
||||||
creativelab5.com
|
creativelab5.com
|
||||||
cristyli.com
|
cristyli.com
|
||||||
@@ -1037,7 +1024,6 @@ dougscripts.com
|
|||||||
doujincafe.com
|
doujincafe.com
|
||||||
dowjones.io
|
dowjones.io
|
||||||
download.dappcdn.com
|
download.dappcdn.com
|
||||||
download.futunn.com
|
|
||||||
dpool.top
|
dpool.top
|
||||||
dpp.org.tw
|
dpp.org.tw
|
||||||
dpr.info
|
dpr.info
|
||||||
@@ -1163,7 +1149,6 @@ expatshield.com
|
|||||||
expecthim.com
|
expecthim.com
|
||||||
expekt.com
|
expekt.com
|
||||||
exploader.net
|
exploader.net
|
||||||
expressnews.com
|
|
||||||
expressvpn.com
|
expressvpn.com
|
||||||
exrates.me
|
exrates.me
|
||||||
extmatrix.com
|
extmatrix.com
|
||||||
@@ -1354,36 +1339,7 @@ funkyimg.com
|
|||||||
furbo.org
|
furbo.org
|
||||||
furinkan.com
|
furinkan.com
|
||||||
furrybar.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
|
futuremessage.org
|
||||||
futustatic.com
|
|
||||||
fututrade.com
|
|
||||||
fututrustee.com
|
|
||||||
fw.cm
|
fw.cm
|
||||||
fxcm-chinese.com
|
fxcm-chinese.com
|
||||||
fxnetworks.com
|
fxnetworks.com
|
||||||
@@ -1406,7 +1362,6 @@ gaoming.net
|
|||||||
gaopi.net
|
gaopi.net
|
||||||
gartlive.com
|
gartlive.com
|
||||||
garudalinux.org
|
garudalinux.org
|
||||||
gate.com
|
|
||||||
gate.io
|
gate.io
|
||||||
gatecoin.com
|
gatecoin.com
|
||||||
gather.com
|
gather.com
|
||||||
@@ -1427,7 +1382,6 @@ generated.photos
|
|||||||
genius.com
|
genius.com
|
||||||
geph.io
|
geph.io
|
||||||
get.app
|
get.app
|
||||||
get.dappcdn.com
|
|
||||||
get.dev
|
get.dev
|
||||||
get.how
|
get.how
|
||||||
get.page
|
get.page
|
||||||
@@ -1785,7 +1739,6 @@ gtv.org
|
|||||||
gtv1.org
|
gtv1.org
|
||||||
gu-chu-sum.org
|
gu-chu-sum.org
|
||||||
guaguass.com
|
guaguass.com
|
||||||
guangming.com.my
|
|
||||||
guishan.org
|
guishan.org
|
||||||
gumroad.com
|
gumroad.com
|
||||||
gunsamerica.com
|
gunsamerica.com
|
||||||
@@ -1876,7 +1829,6 @@ hkgolden.com
|
|||||||
hkgpao.com
|
hkgpao.com
|
||||||
hklts.org.hk
|
hklts.org.hk
|
||||||
hkmap.live
|
hkmap.live
|
||||||
hkong.hk
|
|
||||||
hkopentv.com
|
hkopentv.com
|
||||||
hkpeanut.com
|
hkpeanut.com
|
||||||
hkreporter.com
|
hkreporter.com
|
||||||
@@ -2091,7 +2043,6 @@ itasoftware.com
|
|||||||
itch.io
|
itch.io
|
||||||
itemfix.com
|
itemfix.com
|
||||||
itiger.com
|
itiger.com
|
||||||
itigerup.com
|
|
||||||
itshidden.com
|
itshidden.com
|
||||||
itweet.net
|
itweet.net
|
||||||
iuhrdf.org
|
iuhrdf.org
|
||||||
@@ -2181,7 +2132,6 @@ kenengba.com
|
|||||||
kepard.com
|
kepard.com
|
||||||
kex.com
|
kex.com
|
||||||
keycdn.com
|
keycdn.com
|
||||||
kfor.com
|
|
||||||
khatrimaza.org
|
khatrimaza.org
|
||||||
kichiku-doujinko.com
|
kichiku-doujinko.com
|
||||||
kik.com
|
kik.com
|
||||||
@@ -2231,15 +2181,12 @@ landofhope.tv
|
|||||||
lantern.io
|
lantern.io
|
||||||
laogai.org
|
laogai.org
|
||||||
laogairesearch.org
|
laogairesearch.org
|
||||||
laohu8.com
|
|
||||||
laqingdan.net
|
laqingdan.net
|
||||||
larsgeorge.com
|
larsgeorge.com
|
||||||
lastcombat.com
|
lastcombat.com
|
||||||
lastfm.es
|
lastfm.es
|
||||||
lausan.hk
|
lausan.hk
|
||||||
lbank.info
|
lbank.info
|
||||||
lbctrl.com
|
|
||||||
lbkrs.com
|
|
||||||
ldplayer.net
|
ldplayer.net
|
||||||
ldplayer.tw
|
ldplayer.tw
|
||||||
le-vpn.com
|
le-vpn.com
|
||||||
@@ -2308,15 +2255,6 @@ localdomain.ws
|
|||||||
lockestek.com
|
lockestek.com
|
||||||
login.target.com
|
login.target.com
|
||||||
logos.com.hk
|
logos.com.hk
|
||||||
longbridge.cloud
|
|
||||||
longbridge.com
|
|
||||||
longbridge.global
|
|
||||||
longbridge.hk
|
|
||||||
longbridge.sg
|
|
||||||
longbridgeapp.com
|
|
||||||
longbridgehk.com
|
|
||||||
longportapp.cn
|
|
||||||
longportapp.com
|
|
||||||
longtermly.net
|
longtermly.net
|
||||||
longtoes.com
|
longtoes.com
|
||||||
lookpic.com
|
lookpic.com
|
||||||
@@ -2434,7 +2372,6 @@ mfxmedia.com
|
|||||||
mgoon.com
|
mgoon.com
|
||||||
mgstage.com
|
mgstage.com
|
||||||
mh4u.org
|
mh4u.org
|
||||||
mhwindow.org
|
|
||||||
microvpn.com
|
microvpn.com
|
||||||
mihua.org
|
mihua.org
|
||||||
mij.rip
|
mij.rip
|
||||||
@@ -2504,9 +2441,6 @@ monitorchina.org
|
|||||||
monocloud.me
|
monocloud.me
|
||||||
monster.com
|
monster.com
|
||||||
moodyz.com
|
moodyz.com
|
||||||
moomoo.com
|
|
||||||
moomooequity.com
|
|
||||||
moomootrustee.com
|
|
||||||
moon.fm
|
moon.fm
|
||||||
moonbbs.com
|
moonbbs.com
|
||||||
moonbingo.com
|
moonbingo.com
|
||||||
@@ -2747,13 +2681,6 @@ ooni.io
|
|||||||
ooni.org
|
ooni.org
|
||||||
open.firstory.me
|
open.firstory.me
|
||||||
openai.com
|
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
|
openart.ai
|
||||||
opencritic.com
|
opencritic.com
|
||||||
opendemocracy.net
|
opendemocracy.net
|
||||||
@@ -2819,7 +2746,6 @@ pbs.org
|
|||||||
pbworks.com
|
pbworks.com
|
||||||
pbxes.com
|
pbxes.com
|
||||||
pbxes.org
|
pbxes.org
|
||||||
pcgamesn.com
|
|
||||||
pcgamestorrents.com
|
pcgamestorrents.com
|
||||||
pcij.org
|
pcij.org
|
||||||
pct.org.tw
|
pct.org.tw
|
||||||
@@ -2838,7 +2764,6 @@ peoplenews.tw
|
|||||||
peopo.org
|
peopo.org
|
||||||
perfect-privacy.com
|
perfect-privacy.com
|
||||||
periscope.tv
|
periscope.tv
|
||||||
perma.cc
|
|
||||||
perplexity.ai
|
perplexity.ai
|
||||||
pewresearch.org
|
pewresearch.org
|
||||||
phayul.com
|
phayul.com
|
||||||
@@ -3010,15 +2935,12 @@ qmp4.com
|
|||||||
qoos.com
|
qoos.com
|
||||||
qq.co.za
|
qq.co.za
|
||||||
qstatus.com
|
qstatus.com
|
||||||
qtcard.futunn.com
|
|
||||||
qtcardfthk.futufin.com
|
|
||||||
qtrac.eu
|
qtrac.eu
|
||||||
questvisual.com
|
questvisual.com
|
||||||
quitccp.org
|
quitccp.org
|
||||||
quiz.directory
|
quiz.directory
|
||||||
quora.com
|
quora.com
|
||||||
quoracdn.net
|
quoracdn.net
|
||||||
quote.longbridge.global
|
|
||||||
quran.com
|
quran.com
|
||||||
qz.com
|
qz.com
|
||||||
r-pool.net
|
r-pool.net
|
||||||
@@ -3087,7 +3009,6 @@ rentry.co
|
|||||||
renyurenquan.org
|
renyurenquan.org
|
||||||
resilio.com
|
resilio.com
|
||||||
resistchina.org
|
resistchina.org
|
||||||
restofworld.org
|
|
||||||
retweetrank.com
|
retweetrank.com
|
||||||
reuters.com
|
reuters.com
|
||||||
reutersmedia.net
|
reutersmedia.net
|
||||||
@@ -3161,10 +3082,6 @@ saveuighur.org
|
|||||||
sbme.me
|
sbme.me
|
||||||
sbti.unun.dev
|
sbti.unun.dev
|
||||||
schema.org
|
schema.org
|
||||||
schwab.co.uk
|
|
||||||
schwab.com
|
|
||||||
schwab.com.cn
|
|
||||||
schwab.com.hk
|
|
||||||
scmp.com
|
scmp.com
|
||||||
scramble.io
|
scramble.io
|
||||||
scratch.mit.edu
|
scratch.mit.edu
|
||||||
@@ -3203,7 +3120,6 @@ sexinsex.net
|
|||||||
sf.net
|
sf.net
|
||||||
sfshibao.com
|
sfshibao.com
|
||||||
sftuk.org
|
sftuk.org
|
||||||
sgqt0j.launches.appsflyersdk.com
|
|
||||||
shadeyouvpn.com
|
shadeyouvpn.com
|
||||||
shadowsocks.be
|
shadowsocks.be
|
||||||
shadowsocks.com.hk
|
shadowsocks.com.hk
|
||||||
@@ -3228,7 +3144,6 @@ shodanhq.com
|
|||||||
shooshtime.com
|
shooshtime.com
|
||||||
shopee.tw
|
shopee.tw
|
||||||
shopping.yahoo.co.jp
|
shopping.yahoo.co.jp
|
||||||
shortconn.im.qcloud.com
|
|
||||||
showwe.tw
|
showwe.tw
|
||||||
shutterstock.com
|
shutterstock.com
|
||||||
shwchurch.org
|
shwchurch.org
|
||||||
@@ -3253,7 +3168,6 @@ sinoants.com
|
|||||||
sinoca.com
|
sinoca.com
|
||||||
sinocast.com
|
sinocast.com
|
||||||
sinoinsider.com
|
sinoinsider.com
|
||||||
sinyalee.com
|
|
||||||
sipml5.org
|
sipml5.org
|
||||||
sis001.com
|
sis001.com
|
||||||
site.new
|
site.new
|
||||||
@@ -3323,7 +3237,6 @@ southmongolia.org
|
|||||||
southnews.com.tw
|
southnews.com.tw
|
||||||
southpark.cc.com
|
southpark.cc.com
|
||||||
sowers.org.hk
|
sowers.org.hk
|
||||||
spaceforce.mil
|
|
||||||
spaces.hightail.com
|
spaces.hightail.com
|
||||||
spacex.com
|
spacex.com
|
||||||
spankbang.com
|
spankbang.com
|
||||||
@@ -3414,8 +3327,6 @@ supchina.com
|
|||||||
superpages.com
|
superpages.com
|
||||||
supervpn.net
|
supervpn.net
|
||||||
superzooi.com
|
superzooi.com
|
||||||
support.futunn.com
|
|
||||||
supremecourt.gov
|
|
||||||
suprememastertv.com
|
suprememastertv.com
|
||||||
surfeasy.com
|
surfeasy.com
|
||||||
surfeasy.com.au
|
surfeasy.com.au
|
||||||
@@ -3527,7 +3438,6 @@ thehansindia.com
|
|||||||
thehindu.com
|
thehindu.com
|
||||||
thehun.net
|
thehun.net
|
||||||
theinitium.com
|
theinitium.com
|
||||||
theintercept.com
|
|
||||||
thenewslens.com
|
thenewslens.com
|
||||||
thepiratebay.org
|
thepiratebay.org
|
||||||
theporndude.com
|
theporndude.com
|
||||||
@@ -3606,12 +3516,6 @@ tibettelegraph.com
|
|||||||
tibettimes.net
|
tibettimes.net
|
||||||
tibettruth.com
|
tibettruth.com
|
||||||
tibetwrites.org
|
tibetwrites.org
|
||||||
tigerbbs.cn
|
|
||||||
tigerbbs.com
|
|
||||||
tigerbrokers.com
|
|
||||||
tigerbrokers.net
|
|
||||||
tigerbrokers.nz
|
|
||||||
tigerfintech.com
|
|
||||||
tigervpn.com
|
tigervpn.com
|
||||||
tiktok.com
|
tiktok.com
|
||||||
tiktokcdn-eu.com
|
tiktokcdn-eu.com
|
||||||
@@ -3674,9 +3578,6 @@ totalvpn.com
|
|||||||
tou.tv
|
tou.tv
|
||||||
tpi.org.tw
|
tpi.org.tw
|
||||||
tracfone.com
|
tracfone.com
|
||||||
trade.futunn.com
|
|
||||||
trade.longportapp.com
|
|
||||||
tradeup.com
|
|
||||||
tradingview.com
|
tradingview.com
|
||||||
translate.goog
|
translate.goog
|
||||||
transparency.org
|
transparency.org
|
||||||
@@ -3685,7 +3586,6 @@ trendsmap.com
|
|||||||
tronscan.org
|
tronscan.org
|
||||||
trouw.nl
|
trouw.nl
|
||||||
trt.net.tr
|
trt.net.tr
|
||||||
trtworld.com
|
|
||||||
truebuddha-md.org
|
truebuddha-md.org
|
||||||
trustwallet.com
|
trustwallet.com
|
||||||
truthsocial.com
|
truthsocial.com
|
||||||
@@ -3781,7 +3681,6 @@ twitturk.com
|
|||||||
twitturly.com
|
twitturly.com
|
||||||
twkan.com
|
twkan.com
|
||||||
twreporter.org
|
twreporter.org
|
||||||
twstalker.com
|
|
||||||
twt.tl
|
twt.tl
|
||||||
twtkr.com
|
twtkr.com
|
||||||
twttr.com
|
twttr.com
|
||||||
@@ -3996,7 +3895,6 @@ wanz-factory.com
|
|||||||
warroom.org
|
warroom.org
|
||||||
waselpro.com
|
waselpro.com
|
||||||
washingtonpost.com
|
washingtonpost.com
|
||||||
washingtontimes.com
|
|
||||||
watchinese.com
|
watchinese.com
|
||||||
watchmygf.net
|
watchmygf.net
|
||||||
watchout.tw
|
watchout.tw
|
||||||
@@ -4005,7 +3903,6 @@ wav.tv
|
|||||||
waveprotocol.org
|
waveprotocol.org
|
||||||
waybig.com
|
waybig.com
|
||||||
waymo.com
|
waymo.com
|
||||||
wbrks.com
|
|
||||||
wd.bible
|
wd.bible
|
||||||
wealth.com.tw
|
wealth.com.tw
|
||||||
wearn.com
|
wearn.com
|
||||||
@@ -4015,7 +3912,6 @@ webmproject.org
|
|||||||
webpkgcache.com
|
webpkgcache.com
|
||||||
webrtc.org
|
webrtc.org
|
||||||
websdr.org
|
websdr.org
|
||||||
webshare.io
|
|
||||||
website.new
|
website.new
|
||||||
webwarper.net
|
webwarper.net
|
||||||
wechatlawsuit.com
|
wechatlawsuit.com
|
||||||
@@ -4185,7 +4081,6 @@ xgmyd.com
|
|||||||
xhamster.com
|
xhamster.com
|
||||||
xianjian.tw
|
xianjian.tw
|
||||||
xiaohexie.com
|
xiaohexie.com
|
||||||
xiaohu8.com
|
|
||||||
xiaolan.me
|
xiaolan.me
|
||||||
xiaoma.org
|
xiaoma.org
|
||||||
xiaomi.eu
|
xiaomi.eu
|
||||||
@@ -4210,7 +4105,6 @@ xn--noss43i.com
|
|||||||
xn--oiq.cc
|
xn--oiq.cc
|
||||||
xn--p8j9a0d9c9a.xn--q9jyb4c
|
xn--p8j9a0d9c9a.xn--q9jyb4c
|
||||||
xn--u2u927b.com
|
xn--u2u927b.com
|
||||||
xn--vuqv2cf7wzyig79c.com
|
|
||||||
xnpool.com
|
xnpool.com
|
||||||
xnxx.com
|
xnxx.com
|
||||||
xpud.org
|
xpud.org
|
||||||
@@ -4219,7 +4113,6 @@ xskywalker.com
|
|||||||
xt.com
|
xt.com
|
||||||
xt.pub
|
xt.pub
|
||||||
xtube.com
|
xtube.com
|
||||||
xuan.com.my
|
|
||||||
xuchao.net
|
xuchao.net
|
||||||
xuchao.org
|
xuchao.org
|
||||||
xuehua.us
|
xuehua.us
|
||||||
@@ -4326,8 +4219,6 @@ zhao.1984.city
|
|||||||
zhengjian.org
|
zhengjian.org
|
||||||
zhengwunet.org
|
zhengwunet.org
|
||||||
zhenxiang.biz
|
zhenxiang.biz
|
||||||
zhijianfengyi.cn
|
|
||||||
zhijianfengyi.com
|
|
||||||
zhizhu.top
|
zhizhu.top
|
||||||
zhongguo.ca
|
zhongguo.ca
|
||||||
zhongguotese.net
|
zhongguotese.net
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
202607052256
|
202606132337
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { md5 } from 'digest';
|
||||||
import { readfile, writefile } from 'fs';
|
import { readfile, writefile } from 'fs';
|
||||||
import { isnan } from 'math';
|
import { isnan } from 'math';
|
||||||
import { connect } from 'ubus';
|
import { connect } from 'ubus';
|
||||||
@@ -29,7 +30,10 @@ uci.load(uciconfig);
|
|||||||
|
|
||||||
const uciinfra = 'infra',
|
const uciinfra = 'infra',
|
||||||
ucimain = 'config',
|
ucimain = 'config',
|
||||||
ucicontrol = 'control';
|
ucicontrol = 'control',
|
||||||
|
uciclashapi = 'clash_api',
|
||||||
|
ucintp = 'ntp',
|
||||||
|
ucicache = 'cache';
|
||||||
|
|
||||||
const ucidnssetting = 'dns',
|
const ucidnssetting = 'dns',
|
||||||
ucidnsserver = 'dns_server',
|
ucidnsserver = 'dns_server',
|
||||||
@@ -41,23 +45,63 @@ const uciroutingsetting = 'routing',
|
|||||||
|
|
||||||
const ucinode = 'node';
|
const ucinode = 'node';
|
||||||
const uciruleset = 'ruleset';
|
const uciruleset = 'ruleset';
|
||||||
|
const RULESET_DIR = HP_DIR + '/ruleset';
|
||||||
|
|
||||||
const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainland_china';
|
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];
|
let wan_dns = ubus.call('network.interface', 'status', {'interface': 'wan'})?.['dns-server']?.[0];
|
||||||
if (!wan_dns)
|
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 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';
|
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,
|
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_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;
|
proxy_domain_list;
|
||||||
|
|
||||||
if (routing_mode !== 'custom') {
|
if (routing_mode !== 'custom') {
|
||||||
@@ -93,8 +137,6 @@ if (routing_mode !== 'custom') {
|
|||||||
dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire');
|
dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire');
|
||||||
dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache');
|
dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache');
|
||||||
dns_client_subnet = uci.get(uciconfig, ucidnssetting, 'client_subnet');
|
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 */
|
/* Routing settings */
|
||||||
default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil';
|
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,
|
let self_mark, redirect_port, tproxy_port, tun_name,
|
||||||
tun_addr4, tun_addr6, tun_mtu, tcpip_stack,
|
tun_addr4, tun_addr6, tun_mtu, tcpip_stack,
|
||||||
endpoint_independent_nat, udp_timeout;
|
udp_timeout;
|
||||||
|
|
||||||
if (routing_mode === 'custom')
|
if (routing_mode === 'custom')
|
||||||
udp_timeout = uci.get(uciconfig, uciroutingsetting, 'udp_timeout');
|
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_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126';
|
||||||
tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000';
|
tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000';
|
||||||
tcpip_stack = 'system';
|
tcpip_stack = 'system';
|
||||||
if (routing_mode === 'custom') {
|
if (routing_mode === 'custom')
|
||||||
tcpip_stack = uci.get(uciconfig, uciroutingsetting, 'tcpip_stack') || 'system';
|
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';
|
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 */
|
/* UCI config end */
|
||||||
|
|
||||||
/* Config helper start */
|
/* 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) {
|
function parse_dnsquery(strquery) {
|
||||||
if (type(strquery) !== 'array' || isEmpty(strquery))
|
if (type(strquery) !== 'array' || isEmpty(strquery))
|
||||||
return null;
|
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) {
|
function generate_endpoint(node) {
|
||||||
if (type(node) !== 'object' || isEmpty(node))
|
if (type(node) !== 'object' || isEmpty(node))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
const endpoint = {
|
const endpoint = {
|
||||||
type: node.type,
|
type: node.type,
|
||||||
tag: 'cfg-' + node['.name'] + '-out',
|
tag: get_section_outbound_tag(node['.name']),
|
||||||
address: node.wireguard_local_address,
|
address: node.wireguard_local_address,
|
||||||
mtu: strToInt(node.wireguard_mtu),
|
mtu: strToInt(node.wireguard_mtu),
|
||||||
private_key: node.wireguard_private_key,
|
private_key: node.wireguard_private_key,
|
||||||
@@ -217,13 +425,14 @@ function generate_outbound(node) {
|
|||||||
if (type(node) !== 'object' || isEmpty(node))
|
if (type(node) !== 'object' || isEmpty(node))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
const tls_utls_value = (node.type === 'anytls' && isEmpty(node.tls_utls)) ? 'chrome' : node.tls_utls;
|
||||||
const outbound = {
|
const outbound = {
|
||||||
type: node.type,
|
type: node.type,
|
||||||
tag: 'cfg-' + node['.name'] + '-out',
|
tag: get_section_outbound_tag(node['.name']),
|
||||||
routing_mark: strToInt(self_mark),
|
routing_mark: strToInt(self_mark),
|
||||||
|
|
||||||
server: node.address,
|
server: (node.type !== 'direct') ? node.address : null,
|
||||||
server_port: strToInt(node.port),
|
server_port: (node.type !== 'direct') ? strToInt(node.port) : null,
|
||||||
/* Hysteria(2) */
|
/* Hysteria(2) */
|
||||||
server_ports: node.hysteria_hopping_port,
|
server_ports: node.hysteria_hopping_port,
|
||||||
|
|
||||||
@@ -231,10 +440,6 @@ function generate_outbound(node) {
|
|||||||
user: (node.type === 'ssh') ? node.username : null,
|
user: (node.type === 'ssh') ? node.username : null,
|
||||||
password: node.password,
|
password: node.password,
|
||||||
|
|
||||||
/* Direct */
|
|
||||||
override_address: node.override_address,
|
|
||||||
override_port: strToInt(node.override_port),
|
|
||||||
proxy_protocol: strToInt(node.proxy_protocol),
|
|
||||||
/* AnyTLS */
|
/* AnyTLS */
|
||||||
idle_session_check_interval: strToTime(node.anytls_idle_session_check_interval),
|
idle_session_check_interval: strToTime(node.anytls_idle_session_check_interval),
|
||||||
idle_session_timeout: strToTime(node.anytls_idle_session_timeout),
|
idle_session_timeout: strToTime(node.anytls_idle_session_timeout),
|
||||||
@@ -306,9 +511,9 @@ function generate_outbound(node) {
|
|||||||
config: node.tls_ech_config,
|
config: node.tls_ech_config,
|
||||||
config_path: node.tls_ech_config_path
|
config_path: node.tls_ech_config_path
|
||||||
} : null,
|
} : null,
|
||||||
utls: !isEmpty(node.tls_utls) ? {
|
utls: !isEmpty(tls_utls_value) ? {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
fingerprint: node.tls_utls
|
fingerprint: tls_utls_value
|
||||||
} : null,
|
} : null,
|
||||||
reality: (node.tls_reality === '1') ? {
|
reality: (node.tls_reality === '1') ? {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -364,10 +569,10 @@ function get_outbound(cfg) {
|
|||||||
const node = uci.get(uciconfig, cfg, 'node');
|
const node = uci.get(uciconfig, cfg, 'node');
|
||||||
if (isEmpty(node))
|
if (isEmpty(node))
|
||||||
die(sprintf("%s's node is missing, please check your configuration.", cfg));
|
die(sprintf("%s's node is missing, please check your configuration.", cfg));
|
||||||
else if (node === 'urltest')
|
else if (node in ['urltest', 'selector'])
|
||||||
return 'cfg-' + cfg + '-out';
|
return get_section_outbound_tag(cfg);
|
||||||
else
|
else
|
||||||
return 'cfg-' + node + '-out';
|
return get_section_outbound_tag(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,10 +595,117 @@ function get_ruleset(cfg) {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
let rules = [];
|
let rules = [];
|
||||||
for (let i in cfg)
|
for (let i in cfg) {
|
||||||
push(rules, isEmpty(i) ? null : 'cfg-' + i + '-rule');
|
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;
|
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 */
|
/* Config helper end */
|
||||||
|
|
||||||
const config = {};
|
const config = {};
|
||||||
@@ -409,8 +721,10 @@ config.log = {
|
|||||||
/* NTP */
|
/* NTP */
|
||||||
if (!isEmpty(ntp_server))
|
if (!isEmpty(ntp_server))
|
||||||
config.ntp = {
|
config.ntp = {
|
||||||
enabled: true,
|
enabled: ntp_enabled === '1',
|
||||||
server: ntp_server,
|
server: ntp_server,
|
||||||
|
server_port: strToInt(ntp_server_port),
|
||||||
|
interval: ntp_interval,
|
||||||
detour: 'direct-out',
|
detour: 'direct-out',
|
||||||
domain_resolver: 'default-dns',
|
domain_resolver: 'default-dns',
|
||||||
};
|
};
|
||||||
@@ -520,10 +834,7 @@ if (!isEmpty(main_node)) {
|
|||||||
|
|
||||||
push(config.dns.servers, {
|
push(config.dns.servers, {
|
||||||
tag: 'cfg-' + cfg['.name'] + '-dns',
|
tag: 'cfg-' + cfg['.name'] + '-dns',
|
||||||
type: cfg.type,
|
...parse_custom_dnsserver(cfg),
|
||||||
server: cfg.server,
|
|
||||||
server_port: strToInt(cfg.server_port),
|
|
||||||
path: cfg.path,
|
|
||||||
headers: cfg.headers,
|
headers: cfg.headers,
|
||||||
tls: cfg.tls_sni ? {
|
tls: cfg.tls_sni ? {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -565,7 +876,6 @@ if (!isEmpty(main_node)) {
|
|||||||
user: cfg.user,
|
user: cfg.user,
|
||||||
rule_set: get_ruleset(cfg.rule_set),
|
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_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),
|
invert: strToBool(cfg.invert),
|
||||||
outbound: get_outbound(cfg.outbound),
|
outbound: get_outbound(cfg.outbound),
|
||||||
action: cfg.action,
|
action: cfg.action,
|
||||||
@@ -606,8 +916,6 @@ push(config.inbounds, {
|
|||||||
listen: '::',
|
listen: '::',
|
||||||
listen_port: int(mixed_port),
|
listen_port: int(mixed_port),
|
||||||
udp_timeout: strToTime(udp_timeout),
|
udp_timeout: strToTime(udp_timeout),
|
||||||
sniff: true,
|
|
||||||
sniff_override_destination: strToBool(sniff_override),
|
|
||||||
set_system_proxy: false
|
set_system_proxy: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -617,9 +925,7 @@ if (match(proxy_mode, /redirect/))
|
|||||||
tag: 'redirect-in',
|
tag: 'redirect-in',
|
||||||
|
|
||||||
listen: '::',
|
listen: '::',
|
||||||
listen_port: int(redirect_port),
|
listen_port: int(redirect_port)
|
||||||
sniff: true,
|
|
||||||
sniff_override_destination: strToBool(sniff_override)
|
|
||||||
});
|
});
|
||||||
if (match(proxy_mode, /tproxy/))
|
if (match(proxy_mode, /tproxy/))
|
||||||
push(config.inbounds, {
|
push(config.inbounds, {
|
||||||
@@ -629,9 +935,7 @@ if (match(proxy_mode, /tproxy/))
|
|||||||
listen: '::',
|
listen: '::',
|
||||||
listen_port: int(tproxy_port),
|
listen_port: int(tproxy_port),
|
||||||
network: 'udp',
|
network: 'udp',
|
||||||
udp_timeout: strToTime(udp_timeout),
|
udp_timeout: strToTime(udp_timeout)
|
||||||
sniff: true,
|
|
||||||
sniff_override_destination: strToBool(sniff_override)
|
|
||||||
});
|
});
|
||||||
if (match(proxy_mode, /tun/))
|
if (match(proxy_mode, /tun/))
|
||||||
push(config.inbounds, {
|
push(config.inbounds, {
|
||||||
@@ -642,11 +946,8 @@ if (match(proxy_mode, /tun/))
|
|||||||
address: (ipv6_support === '1') ? [tun_addr4, tun_addr6] : [tun_addr4],
|
address: (ipv6_support === '1') ? [tun_addr4, tun_addr6] : [tun_addr4],
|
||||||
mtu: strToInt(tun_mtu),
|
mtu: strToInt(tun_mtu),
|
||||||
auto_route: false,
|
auto_route: false,
|
||||||
endpoint_independent_nat: strToBool(endpoint_independent_nat),
|
|
||||||
udp_timeout: strToTime(udp_timeout),
|
udp_timeout: strToTime(udp_timeout),
|
||||||
stack: tcpip_stack,
|
stack: tcpip_stack
|
||||||
sniff: true,
|
|
||||||
sniff_override_destination: strToBool(sniff_override)
|
|
||||||
});
|
});
|
||||||
/* Inbound end */
|
/* Inbound end */
|
||||||
|
|
||||||
@@ -671,14 +972,16 @@ if (!isEmpty(main_node)) {
|
|||||||
let urltest_nodes = [];
|
let urltest_nodes = [];
|
||||||
|
|
||||||
if (main_node === 'urltest') {
|
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_interval = uci.get(uciconfig, ucimain, 'main_urltest_interval');
|
||||||
const main_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_urltest_tolerance');
|
const main_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_urltest_tolerance');
|
||||||
|
|
||||||
push(config.outbounds, {
|
push(config.outbounds, {
|
||||||
type: 'urltest',
|
type: 'urltest',
|
||||||
tag: 'main-out',
|
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),
|
interval: strToTime(main_urltest_interval),
|
||||||
tolerance: strToInt(main_urltest_tolerance),
|
tolerance: strToInt(main_urltest_tolerance),
|
||||||
idle_timeout: (strToInt(main_urltest_interval) > 1800) ? `${main_urltest_interval * 2}s` : null,
|
idle_timeout: (strToInt(main_urltest_interval) > 1800) ? `${main_urltest_interval * 2}s` : null,
|
||||||
@@ -687,23 +990,31 @@ if (!isEmpty(main_node)) {
|
|||||||
} else {
|
} else {
|
||||||
const main_node_cfg = uci.get_all(uciconfig, main_node) || {};
|
const main_node_cfg = uci.get_all(uciconfig, main_node) || {};
|
||||||
if (main_node_cfg.type === 'wireguard') {
|
if (main_node_cfg.type === 'wireguard') {
|
||||||
push(config.endpoints, generate_endpoint(main_node_cfg));
|
const main_endpoint = generate_endpoint(main_node_cfg);
|
||||||
config.endpoints[length(config.endpoints)-1].tag = 'main-out';
|
if (main_endpoint) {
|
||||||
|
main_endpoint.tag = 'main-out';
|
||||||
|
push(config.endpoints, main_endpoint);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
push(config.outbounds, generate_outbound(main_node_cfg));
|
const main_outbound = generate_outbound(main_node_cfg);
|
||||||
config.outbounds[length(config.outbounds)-1].tag = 'main-out';
|
if (main_outbound) {
|
||||||
|
main_outbound.tag = 'main-out';
|
||||||
|
push(config.outbounds, main_outbound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (main_udp_node === 'urltest') {
|
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_interval = uci.get(uciconfig, ucimain, 'main_udp_urltest_interval');
|
||||||
const main_udp_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_udp_urltest_tolerance');
|
const main_udp_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_udp_urltest_tolerance');
|
||||||
|
|
||||||
push(config.outbounds, {
|
push(config.outbounds, {
|
||||||
type: 'urltest',
|
type: 'urltest',
|
||||||
tag: 'main-udp-out',
|
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),
|
interval: strToTime(main_udp_urltest_interval),
|
||||||
tolerance: strToInt(main_udp_urltest_tolerance),
|
tolerance: strToInt(main_udp_urltest_tolerance),
|
||||||
idle_timeout: (strToInt(main_udp_urltest_interval) > 1800) ? `${main_udp_urltest_interval * 2}s` : null,
|
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) {
|
} else if (dedicated_udp_node) {
|
||||||
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
|
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
|
||||||
if (main_udp_node_cfg.type === 'wireguard') {
|
if (main_udp_node_cfg.type === 'wireguard') {
|
||||||
push(config.endpoints, generate_endpoint(main_udp_node_cfg));
|
const main_udp_endpoint = generate_endpoint(main_udp_node_cfg);
|
||||||
config.endpoints[length(config.endpoints)-1].tag = 'main-udp-out';
|
if (main_udp_endpoint) {
|
||||||
|
main_udp_endpoint.tag = 'main-udp-out';
|
||||||
|
push(config.endpoints, main_udp_endpoint);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
push(config.outbounds, generate_outbound(main_udp_node_cfg));
|
const main_udp_outbound = generate_outbound(main_udp_node_cfg);
|
||||||
config.outbounds[length(config.outbounds)-1].tag = 'main-udp-out';
|
if (main_udp_outbound) {
|
||||||
|
main_udp_outbound.tag = 'main-udp-out';
|
||||||
|
push(config.outbounds, main_udp_outbound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i in urltest_nodes) {
|
for (let i in urltest_nodes) {
|
||||||
const urltest_node = uci.get_all(uciconfig, i) || {};
|
const urltest_node = uci.get_all(uciconfig, i) || {};
|
||||||
|
if (isEmpty(urltest_node))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (urltest_node.type === 'wireguard') {
|
if (urltest_node.type === 'wireguard') {
|
||||||
push(config.endpoints, generate_endpoint(urltest_node));
|
const endpoint = generate_endpoint(urltest_node);
|
||||||
config.endpoints[length(config.endpoints)-1].tag = 'cfg-' + i + '-out';
|
if (endpoint) {
|
||||||
|
endpoint.tag = get_section_outbound_tag(i);
|
||||||
|
push(config.endpoints, endpoint);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
push(config.outbounds, generate_outbound(urltest_node));
|
const outbound = generate_outbound(urltest_node);
|
||||||
config.outbounds[length(config.outbounds)-1].tag = 'cfg-' + i + '-out';
|
if (outbound) {
|
||||||
|
outbound.tag = get_section_outbound_tag(i);
|
||||||
|
push(config.outbounds, outbound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!isEmpty(default_outbound)) {
|
} else if (!isEmpty(default_outbound)) {
|
||||||
@@ -739,37 +1065,63 @@ if (!isEmpty(main_node)) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (cfg.node === 'urltest') {
|
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, {
|
push(config.outbounds, {
|
||||||
type: 'urltest',
|
type: 'urltest',
|
||||||
tag: 'cfg-' + cfg['.name'] + '-out',
|
tag: get_section_outbound_tag(cfg['.name']),
|
||||||
outbounds: map(cfg.urltest_nodes, (k) => `cfg-${k}-out`),
|
outbounds: map(urltest_list, (k) => get_section_outbound_tag(k)),
|
||||||
url: cfg.urltest_url,
|
url: cfg.urltest_url,
|
||||||
interval: strToTime(cfg.urltest_interval),
|
interval: strToTime(cfg.urltest_interval),
|
||||||
tolerance: strToInt(cfg.urltest_tolerance),
|
tolerance: strToInt(cfg.urltest_tolerance),
|
||||||
idle_timeout: strToTime(cfg.urltest_idle_timeout),
|
idle_timeout: strToTime(cfg.urltest_idle_timeout),
|
||||||
interrupt_exist_connections: strToBool(cfg.urltest_interrupt_exist_connections)
|
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 {
|
} else {
|
||||||
const outbound = uci.get_all(uciconfig, cfg.node) || {};
|
const outbound = uci.get_all(uciconfig, cfg.node) || {};
|
||||||
|
if (isEmpty(outbound))
|
||||||
|
return;
|
||||||
|
|
||||||
if (outbound.type === 'wireguard') {
|
if (outbound.type === 'wireguard') {
|
||||||
push(config.endpoints, generate_endpoint(outbound));
|
const endpoint = generate_endpoint(outbound);
|
||||||
config.endpoints[length(config.endpoints)-1].bind_interface = cfg.bind_interface;
|
if (!endpoint)
|
||||||
config.endpoints[length(config.endpoints)-1].detour = get_outbound(cfg.outbound);
|
return;
|
||||||
|
|
||||||
|
endpoint.bind_interface = cfg.bind_interface;
|
||||||
|
endpoint.detour = get_outbound(cfg.outbound);
|
||||||
if (cfg.domain_resolver)
|
if (cfg.domain_resolver)
|
||||||
config.endpoints[length(config.endpoints)-1].domain_resolver = {
|
endpoint.domain_resolver = {
|
||||||
server: get_resolver(cfg.domain_resolver),
|
server: get_resolver(cfg.domain_resolver),
|
||||||
strategy: cfg.domain_strategy
|
strategy: cfg.domain_strategy
|
||||||
};
|
};
|
||||||
|
push(config.endpoints, endpoint);
|
||||||
} else {
|
} else {
|
||||||
push(config.outbounds, generate_outbound(outbound));
|
const routed_outbound = generate_outbound(outbound);
|
||||||
config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface;
|
if (!routed_outbound)
|
||||||
config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound);
|
return;
|
||||||
|
|
||||||
|
routed_outbound.bind_interface = cfg.bind_interface;
|
||||||
|
routed_outbound.detour = get_outbound(cfg.outbound);
|
||||||
if (cfg.domain_resolver)
|
if (cfg.domain_resolver)
|
||||||
config.outbounds[length(config.outbounds)-1].domain_resolver = {
|
routed_outbound.domain_resolver = {
|
||||||
server: get_resolver(cfg.domain_resolver),
|
server: get_resolver(cfg.domain_resolver),
|
||||||
strategy: cfg.domain_strategy
|
strategy: cfg.domain_strategy
|
||||||
};
|
};
|
||||||
|
push(config.outbounds, routed_outbound);
|
||||||
}
|
}
|
||||||
push(routing_nodes, cfg.node);
|
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))) {
|
for (let i in filter(urltest_nodes, (l) => !~index(routing_nodes, l))) {
|
||||||
const urltest_node = uci.get_all(uciconfig, i) || {};
|
const urltest_node = uci.get_all(uciconfig, i) || {};
|
||||||
if (urltest_node.type === 'wireguard')
|
if (isEmpty(urltest_node))
|
||||||
push(config.endpoints, generate_endpoint(urltest_node));
|
continue;
|
||||||
else
|
|
||||||
push(config.outbounds, generate_outbound(urltest_node));
|
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',
|
inbound: 'dns-in',
|
||||||
action: 'hijack-dns'
|
action: 'hijack-dns'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'sniff'
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* leave for sing-box 1.13.0
|
|
||||||
* {
|
|
||||||
* action: 'sniff'
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
],
|
],
|
||||||
rule_set: [],
|
rule_set: [],
|
||||||
auto_detect_interface: isEmpty(default_interface) ? true : null,
|
auto_detect_interface: isEmpty(default_interface) ? true : null,
|
||||||
@@ -866,6 +1223,7 @@ if (!isEmpty(main_node)) {
|
|||||||
tag: 'geoip-cn',
|
tag: 'geoip-cn',
|
||||||
format: 'binary',
|
format: 'binary',
|
||||||
url: 'https://fastly.jsdelivr.net/gh/1715173329/IPCIDR-CHINA@rule-set/cn.srs',
|
url: 'https://fastly.jsdelivr.net/gh/1715173329/IPCIDR-CHINA@rule-set/cn.srs',
|
||||||
|
path: ruleset_default_path('geoip-cn', 'binary'),
|
||||||
download_detour: 'main-out'
|
download_detour: 'main-out'
|
||||||
});
|
});
|
||||||
push(config.route.rule_set, {
|
push(config.route.rule_set, {
|
||||||
@@ -873,6 +1231,7 @@ if (!isEmpty(main_node)) {
|
|||||||
tag: 'geosite-cn',
|
tag: 'geosite-cn',
|
||||||
format: 'binary',
|
format: 'binary',
|
||||||
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-cn.srs',
|
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'
|
download_detour: 'main-out'
|
||||||
});
|
});
|
||||||
push(config.route.rule_set, {
|
push(config.route.rule_set, {
|
||||||
@@ -880,6 +1239,7 @@ if (!isEmpty(main_node)) {
|
|||||||
tag: 'geosite-noncn',
|
tag: 'geosite-noncn',
|
||||||
format: 'binary',
|
format: 'binary',
|
||||||
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-!cn.srs',
|
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'
|
download_detour: 'main-out'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -924,17 +1284,25 @@ if (!isEmpty(main_node)) {
|
|||||||
user: cfg.user,
|
user: cfg.user,
|
||||||
rule_set: get_ruleset(cfg.rule_set),
|
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_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),
|
invert: strToBool(cfg.invert),
|
||||||
action: cfg.action,
|
action: cfg.action,
|
||||||
outbound: get_outbound(cfg.outbound),
|
outbound: (cfg.action === 'route') ? get_outbound(cfg.outbound) : null,
|
||||||
override_address: cfg.override_address,
|
server: (cfg.action === 'resolve') ? get_resolver(cfg.resolve_server) : null,
|
||||||
override_port: strToInt(cfg.override_port),
|
strategy: (cfg.action === 'resolve') ? cfg.resolve_strategy : null,
|
||||||
udp_disable_domain_unmapping: strToBool(cfg.udp_disable_domain_unmapping),
|
disable_cache: (cfg.action === 'resolve') ? strToBool(cfg.resolve_disable_cache) : null,
|
||||||
udp_connect: strToBool(cfg.udp_connect),
|
rewrite_ttl: (cfg.action === 'resolve') ? strToInt(cfg.resolve_rewrite_ttl) : null,
|
||||||
udp_timeout: strToTime(cfg.udp_timeout),
|
client_subnet: (cfg.action === 'resolve') ? cfg.resolve_client_subnet : null,
|
||||||
tls_fragment: strToBool(cfg.tls_fragment),
|
override_address: (cfg.action in ['route', 'route-options']) ? cfg.override_address : null,
|
||||||
tls_fragment_fallback_delay: strToTime(cfg.tls_fragment_fallback_delay),
|
override_port: (cfg.action in ['route', 'route-options']) ? strToInt(cfg.override_port) : null,
|
||||||
tls_record_fragment: strToBool(cfg.tls_record_fragment)
|
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')
|
if (cfg.enabled !== '1')
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
const tag = !isEmpty(cfg.tag) ? cfg.tag : ('cfg-' + cfg['.name'] + '-rule');
|
||||||
|
|
||||||
push(config.route.rule_set, {
|
push(config.route.rule_set, {
|
||||||
type: cfg.type,
|
type: cfg.type,
|
||||||
tag: 'cfg-' + cfg['.name'] + '-rule',
|
tag: tag,
|
||||||
format: cfg.format,
|
format: cfg.format,
|
||||||
path: cfg.path,
|
|
||||||
url: cfg.url,
|
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),
|
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 */
|
/* Experimental start */
|
||||||
if (routing_mode in ['bypass_mainland_china', 'custom']) {
|
if (routing_mode in ['bypass_mainland_china', 'custom']) {
|
||||||
config.experimental = {
|
config.experimental = {
|
||||||
|
clash_api,
|
||||||
cache_file: {
|
cache_file: {
|
||||||
enabled: true,
|
enabled: cache_file_enabled !== '0',
|
||||||
path: RUN_DIR + '/cache.db',
|
path: cache_file_path || (HP_DIR + '/cache.db'),
|
||||||
store_rdrc: strToBool(cache_file_store_rdrc),
|
store_rdrc: strToBool(cache_store_rdrc),
|
||||||
rdrc_timeout: strToTime(cache_file_rdrc_timeout),
|
rdrc_timeout: strToTime(cache_rdrc_timeout),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/* Experimental end */
|
/* Experimental end */
|
||||||
|
|
||||||
system('mkdir -p ' + RUN_DIR);
|
system('mkdir -p ' + RUN_DIR);
|
||||||
|
system('mkdir -p ' + RULESET_DIR);
|
||||||
writefile(RUN_DIR + '/sing-box-c.json', sprintf('%.J\n', removeBlankAttrs(config)));
|
writefile(RUN_DIR + '/sing-box-c.json', sprintf('%.J\n', removeBlankAttrs(config)));
|
||||||
@@ -67,7 +67,7 @@ uci.foreach(uciconfig, uciserver, (cfg) => {
|
|||||||
password: cfg.hysteria_obfs_password
|
password: cfg.hysteria_obfs_password
|
||||||
} : cfg.hysteria_obfs_password,
|
} : cfg.hysteria_obfs_password,
|
||||||
recv_window_conn: strToInt(cfg.hysteria_recv_window_conn),
|
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),
|
max_conn_client: strToInt(cfg.hysteria_max_conn_client),
|
||||||
disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery),
|
disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery),
|
||||||
ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth),
|
ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth),
|
||||||
|
|||||||
Regular → Executable
+22
-3
@@ -66,15 +66,34 @@ export function getTime(epoch) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function wGET(url, ua) {
|
export function wGETResponse(url, ua) {
|
||||||
if (!url || type(url) !== 'string')
|
if (!url || type(url) !== 'string')
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (!ua)
|
if (!ua)
|
||||||
ua = 'Wget/1.21 (HomeProxy, like v2rayN)';
|
ua = 'Wget/1.21 (HomeProxy, like v2rayN)';
|
||||||
|
|
||||||
const output = executeCommand(`/usr/bin/wget -qO- --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
|
const output = executeCommand(`/usr/bin/wget -O- -S --user-agent ${shellQuote(ua)} --timeout=10 ${shellQuote(url)}`) || {};
|
||||||
return trim(output.stdout);
|
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 */
|
/* Utilities end */
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,34 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { popen } from 'fs';
|
||||||
import { cursor } from 'uci';
|
import { cursor } from 'uci';
|
||||||
import { isEmpty, parseURL, validation } from 'homeproxy';
|
import { isEmpty, parseURL } from 'homeproxy';
|
||||||
|
|
||||||
const uci = cursor();
|
const uci = cursor();
|
||||||
|
|
||||||
const uciconfig = 'homeproxy';
|
const uciconfig = 'homeproxy';
|
||||||
uci.load(uciconfig);
|
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',
|
const uciinfra = 'infra',
|
||||||
ucimigration = 'migration',
|
ucimigration = 'migration',
|
||||||
ucimain = 'config',
|
ucimain = 'config',
|
||||||
@@ -25,8 +45,16 @@ const uciinfra = 'infra',
|
|||||||
ucirouting = 'routing',
|
ucirouting = 'routing',
|
||||||
uciroutingnode = 'routing_node',
|
uciroutingnode = 'routing_node',
|
||||||
uciroutingrule = 'routing_rule',
|
uciroutingrule = 'routing_rule',
|
||||||
|
uciclashapi = 'clash_api',
|
||||||
|
ucintp = 'ntp',
|
||||||
|
ucicache = 'cache',
|
||||||
uciserver = 'server';
|
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 */
|
/* chinadns-ng has been removed */
|
||||||
if (uci.get(uciconfig, uciinfra, 'china_dns_port'))
|
if (uci.get(uciconfig, uciinfra, 'china_dns_port'))
|
||||||
uci.delete(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') {
|
if (type(china_dns_server) === 'array') {
|
||||||
uci.set(uciconfig, ucimain, 'china_dns_server', china_dns_server[0]);
|
uci.set(uciconfig, ucimain, 'china_dns_server', china_dns_server[0]);
|
||||||
} else {
|
} else {
|
||||||
if (china_dns_server === 'wan_114')
|
if (match(china_dns_server, /,/))
|
||||||
uci.set(uciconfig, ucimain, 'china_dns_server', '114.114.114.114');
|
|
||||||
else if (match(china_dns_server, /,/))
|
|
||||||
uci.set(uciconfig, ucimain, 'china_dns_server', split(china_dns_server, ',')[0]);
|
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')))
|
if (!isEmpty(uci.get(uciconfig, uciinfra, 'tun_gso')))
|
||||||
uci.delete(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 */
|
/* create migration section */
|
||||||
if (!uci.get(uciconfig, ucimigration))
|
if (!uci.get(uciconfig, ucimigration))
|
||||||
uci.set(uciconfig, ucimigration, uciconfig);
|
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')))
|
if (isEmpty(uci.get(uciconfig, uciserver, 'log_level')))
|
||||||
uci.set(uciconfig, uciserver, 'log_level', 'warn');
|
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 */
|
/* empty value defaults to all ports now */
|
||||||
if (uci.get(uciconfig, ucimain, 'routing_port') === 'all')
|
if (uci.get(uciconfig, ucimain, 'routing_port') === 'all')
|
||||||
uci.delete(uciconfig, ucimain, 'routing_port');
|
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 */
|
/* server options */
|
||||||
/* auto_firewall was moved into server options */
|
/* auto_firewall was moved into server options */
|
||||||
const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall');
|
const auto_firewall = uci.get(uciconfig, uciserver, 'auto_firewall');
|
||||||
@@ -246,6 +404,13 @@ if (!isEmpty(auto_firewall))
|
|||||||
uci.delete(uciconfig, uciserver, 'auto_firewall');
|
uci.delete(uciconfig, uciserver, 'auto_firewall');
|
||||||
|
|
||||||
uci.foreach(uciconfig, uciserver, (cfg) => {
|
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 */
|
/* auto_firewall was moved into server options */
|
||||||
if (auto_firewall === '1')
|
if (auto_firewall === '1')
|
||||||
uci.set(uciconfig, cfg['.name'], 'firewall' , '1');
|
uci.set(uciconfig, cfg['.name'], 'firewall' , '1');
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -21,64 +21,65 @@ to_upper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_list_update() {
|
check_list_update() {
|
||||||
local listtype="$1"
|
local LIST_FILE="$1"
|
||||||
local listrepo="$2"
|
local REPO_NAME="$2"
|
||||||
local listref="$3"
|
local REPO_BRANCH="$3"
|
||||||
local listname="$4"
|
local REPO_FILE="$4"
|
||||||
local lock="$RUN_DIR/update_resources-$listtype.lock"
|
local LOCK_FILE="$RUN_DIR/update_resources-$LIST_FILE.lock"
|
||||||
local github_token="$(uci -q get homeproxy.config.github_token)"
|
local GITHUB_TOKEN="$(uci -q get homeproxy.config.github_token)"
|
||||||
local wget="wget --timeout=10 -q"
|
|
||||||
|
|
||||||
exec 200>"$lock"
|
if ! lock -n "$LOCK_FILE" 2>"/dev/null"; then
|
||||||
if ! flock -n 200 &> "/dev/null"; then
|
log "[$(to_upper "$LIST_FILE")] A task is already running."
|
||||||
log "[$(to_upper "$listtype")] A task is already running."
|
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -z "$github_token" ] || github_token="--header=Authorization: Bearer $github_token"
|
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")
|
||||||
local list_info="$($wget "${github_token:--q}" -O- "https://api.github.com/repos/$listrepo/commits?sha=$listref&path=$listname&per_page=1")"
|
if [ -z "$NEW_VER" ]; then
|
||||||
local list_sha="$(echo -e "$list_info" | jsonfilter -qe "@[0].sha")"
|
log "[$(to_upper "$LIST_FILE")] Failed to get the latest version, please retry later."
|
||||||
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."
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local local_list_ver="$(cat "$RESOURCES_DIR/$listtype.ver" 2>"/dev/null" || echo "NOT FOUND")"
|
local OLD_VER=$(cat "$RESOURCES_DIR/$LIST_FILE.ver" 2>/dev/null || echo "NOT FOUND")
|
||||||
if [ "$local_list_ver" = "$list_ver" ]; then
|
if [ "$OLD_VER" = "$NEW_VER" ]; then
|
||||||
log "[$(to_upper "$listtype")] Current version: $list_ver."
|
log "[$(to_upper "$LIST_FILE")] Current version: $NEW_VER."
|
||||||
log "[$(to_upper "$listtype")] You're already at the latest version."
|
log "[$(to_upper "$LIST_FILE")] You're already at the latest version."
|
||||||
|
|
||||||
return 3
|
return 3
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
if ! $wget "https://fastly.jsdelivr.net/gh/$listrepo@$list_sha/$listname" -O "$RUN_DIR/$listname" || [ ! -s "$RUN_DIR/$listname" ]; then
|
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/$listname"
|
rm -f "$RUN_DIR/$REPO_FILE"
|
||||||
log "[$(to_upper "$listtype")] Update failed."
|
log "[$(to_upper "$LIST_FILE")] Update failed."
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv -f "$RUN_DIR/$listname" "$RESOURCES_DIR/$listtype.${listname##*.}"
|
mv -f "$RUN_DIR/$REPO_FILE" "$RESOURCES_DIR/$LIST_FILE.${REPO_FILE##*.}"
|
||||||
echo -e "$list_ver" > "$RESOURCES_DIR/$listtype.ver"
|
echo -e "$NEW_VER" > "$RESOURCES_DIR/$LIST_FILE.ver"
|
||||||
log "[$(to_upper "$listtype")] Successfully updated."
|
log "[$(to_upper "$LIST_FILE")] Successfully updated."
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
"china_ip4")
|
"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")
|
"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")
|
"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")
|
"china_list")
|
||||||
check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "direct-list.txt" && \
|
check_list_update "$1" "Loyalsoldier/surge-rules" "release" "direct.txt" && \
|
||||||
sed -i -e "s/full://g" -e "/:/d" "$RESOURCES_DIR/china_list.txt"
|
sed -i "s/^\.//g" "$RESOURCES_DIR/china_list.txt"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "Usage: $0 <china_ip4 / china_ip6 / gfw_list / china_list>"
|
echo -e "Usage: $0 <china_ip4 / china_ip6 / gfw_list / china_list>"
|
||||||
|
|||||||
@@ -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
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -27,13 +27,82 @@ log() {
|
|||||||
echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH"
|
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() {
|
start_service() {
|
||||||
|
init_logs
|
||||||
|
|
||||||
config_load "$CONF"
|
config_load "$CONF"
|
||||||
|
setup_auto_update_cron
|
||||||
|
|
||||||
local routing_mode proxy_mode
|
local routing_mode proxy_mode
|
||||||
config_get routing_mode "config" "routing_mode" "bypass_mainland_china"
|
config_get routing_mode "config" "routing_mode" "bypass_mainland_china"
|
||||||
config_get proxy_mode "config" "proxy_mode" "redirect_tproxy"
|
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
|
local outbound_node
|
||||||
if [ "$routing_mode" != "custom" ]; then
|
if [ "$routing_mode" != "custom" ]; then
|
||||||
config_get outbound_node "config" "main_node" "nil"
|
config_get outbound_node "config" "main_node" "nil"
|
||||||
@@ -48,8 +117,6 @@ start_service() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$RUN_DIR"
|
|
||||||
|
|
||||||
if [ "$outbound_node" != "nil" ]; then
|
if [ "$outbound_node" != "nil" ]; then
|
||||||
# Generate/Validate client config
|
# Generate/Validate client config
|
||||||
ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH"
|
ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH"
|
||||||
@@ -62,16 +129,6 @@ start_service() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
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
|
# DNSMasq rules
|
||||||
local ipv6_support dns_port
|
local ipv6_support dns_port
|
||||||
config_get_bool ipv6_support "config" "ipv6_support" "0"
|
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" \
|
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"
|
"$HP_DIR/resources/proxy_list.txt" > "$DNSMASQ_DIR/proxy_list.conf"
|
||||||
fi
|
fi
|
||||||
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
|
|
||||||
|
|
||||||
# Setup routing table
|
# Setup routing table
|
||||||
local table_mark
|
local table_mark
|
||||||
config_get table_mark "infra" "table_mark" "100"
|
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 "sing-box-c" log procfs
|
||||||
procd_add_jail_mount "$RUN_DIR/sing-box-c.json"
|
procd_add_jail_mount "$RUN_DIR/sing-box-c.json"
|
||||||
procd_add_jail_mount_rw "$RUN_DIR/sing-box-c.log"
|
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 "$HP_DIR/certs/"
|
||||||
procd_add_jail_mount "/etc/ssl/"
|
procd_add_jail_mount "/etc/ssl/"
|
||||||
procd_add_jail_mount "/etc/localtime"
|
procd_add_jail_mount "/etc/localtime"
|
||||||
@@ -226,19 +282,15 @@ start_service() {
|
|||||||
procd_close_instance
|
procd_close_instance
|
||||||
|
|
||||||
case "$routing_mode" in
|
case "$routing_mode" in
|
||||||
"bypass_mainland_china")
|
|
||||||
# Prepare cache db
|
|
||||||
[ -e "$RUN_DIR/cache.db" ] || touch "$RUN_DIR/cache.db"
|
|
||||||
;;
|
|
||||||
"custom")
|
"custom")
|
||||||
# Prepare ruleset directory
|
# Prepare ruleset directory
|
||||||
[ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset"
|
[ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset"
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
if [ "$server_enabled" = "1" ]; then
|
||||||
echo > "$RUN_DIR/sing-box-s.log"
|
: > "$RUN_DIR/sing-box-s.log"
|
||||||
mkdir -p "$HP_DIR/certs"
|
mkdir -p "$HP_DIR/certs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -249,6 +301,7 @@ start_service() {
|
|||||||
ucode "$HP_DIR/scripts/firewall_pre.uc"
|
ucode "$HP_DIR/scripts/firewall_pre.uc"
|
||||||
[ "$outbound_node" = "nil" ] || utpl -S "$HP_DIR/scripts/firewall_post.ut" > "$RUN_DIR/fw4_post.nft"
|
[ "$outbound_node" = "nil" ] || utpl -S "$HP_DIR/scripts/firewall_post.ut" > "$RUN_DIR/fw4_post.nft"
|
||||||
fw4 reload >"/dev/null" 2>&1
|
fw4 reload >"/dev/null" 2>&1
|
||||||
|
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
|
||||||
|
|
||||||
log "sing-box $(sing-box version -n) started."
|
log "sing-box $(sing-box version -n) started."
|
||||||
}
|
}
|
||||||
@@ -308,8 +361,8 @@ stop_service() {
|
|||||||
rm -rf "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" "$DNSMASQ_DIR"
|
rm -rf "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" "$DNSMASQ_DIR"
|
||||||
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
|
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1
|
||||||
|
|
||||||
rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-c.log" \
|
rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-s.json"
|
||||||
"$RUN_DIR/sing-box-s.json" "$RUN_DIR/sing-box-s.log"
|
init_logs
|
||||||
|
|
||||||
log "Service stopped."
|
log "Service stopped."
|
||||||
}
|
}
|
||||||
|
|||||||
Regular → Executable
+30
@@ -3,6 +3,36 @@
|
|||||||
[ -f "/www/luci-static/resources/icons/loading.gif" ] && \
|
[ -f "/www/luci-static/resources/icons/loading.gif" ] && \
|
||||||
sed -i "s,/loading.svg,/loading.gif,g" "/www/luci-static/resources/view/homeproxy/status.js"
|
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"
|
uci -q batch <<-EOF >"/dev/null"
|
||||||
delete firewall.homeproxy_pre
|
delete firewall.homeproxy_pre
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
File mode changed.
@@ -9,6 +9,7 @@
|
|||||||
"/var/run/homeproxy/sing-box-s.log": [ "read" ]
|
"/var/run/homeproxy/sing-box-s.log": [ "read" ]
|
||||||
},
|
},
|
||||||
"ubus": {
|
"ubus": {
|
||||||
|
"rc": [ "*" ],
|
||||||
"service": [ "list" ],
|
"service": [ "list" ],
|
||||||
"luci.homeproxy": [ "*" ]
|
"luci.homeproxy": [ "*" ]
|
||||||
},
|
},
|
||||||
@@ -16,7 +17,11 @@
|
|||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
"file": {
|
"file": {
|
||||||
"/tmp/homeproxy_certificate.tmp": [ "write" ]
|
"/tmp/homeproxy_certificate.tmp": [ "write" ],
|
||||||
|
"/tmp/homeproxy_panel.tmp": [ "write" ]
|
||||||
|
},
|
||||||
|
"ubus": {
|
||||||
|
"rc": [ "*" ]
|
||||||
},
|
},
|
||||||
"uci": [ "homeproxy" ]
|
"uci": [ "homeproxy" ]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,183 @@
|
|||||||
|
# luci-app-pingpacket
|
||||||
|
|
||||||
|
`luci-app-pingpacket` 是一个面向 OpenWrt 的 LuCI 状态监控插件,用于持续观察国内直连质量和国外经代理访问质量,并提供统计、日志和快速排障能力。
|
||||||
|
|
||||||
|
## 界面截图
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%" align="center">
|
||||||
|
<img src="jpg/1.png" alt="Ping丢包监控总览" width="100%">
|
||||||
|
<br>
|
||||||
|
<sub>监控总览</sub>
|
||||||
|
</td>
|
||||||
|
<td width="50%" align="center">
|
||||||
|
<img src="jpg/2.png" alt="Ping丢包监控设置" width="100%">
|
||||||
|
<br>
|
||||||
|
<sub>监控设置</sub>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" align="center">
|
||||||
|
<img src="jpg/3.png" alt="Ping丢包监控运行日志" width="78%">
|
||||||
|
<br>
|
||||||
|
<sub>运行日志</sub>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## 适用平台
|
||||||
|
|
||||||
|
- 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`,安装脚本会尽量兼容处理
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
@@ -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)))
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
<%+header%>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pp-log-page {
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 8px 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-hero {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 24px 26px;
|
||||||
|
margin: 12px 0 18px;
|
||||||
|
border-radius: 18px;
|
||||||
|
color: #fff;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(255, 255, 255, 0.2), transparent 34%),
|
||||||
|
linear-gradient(135deg, #10615b, #0f766e 60%, #164e63);
|
||||||
|
box-shadow: 0 18px 38px rgba(15, 118, 110, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-copy {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-kicker {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.78;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 1.15;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-subtitle {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-summary {
|
||||||
|
min-width: 250px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-summary-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-summary-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-family: "JetBrains Mono", "Consolas", "Menlo", monospace;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-meta,
|
||||||
|
.pp-log-panel {
|
||||||
|
background: linear-gradient(180deg, #ffffff, #fbfdff);
|
||||||
|
border: 1px solid #dde8f6;
|
||||||
|
box-shadow: 0 10px 28px rgba(27, 73, 124, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-meta {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-meta-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6d7d92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-meta-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #24364d;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-family: "JetBrains Mono", "Consolas", "Menlo", monospace;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-panel {
|
||||||
|
padding: 22px;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 14px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-refresh {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #344a63;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-refresh input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-note {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6f8198;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-button {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #a34717;
|
||||||
|
background: linear-gradient(135deg, #ffe0c7, #ffd2b0);
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 10px 24px rgba(240, 123, 45, 0.14);
|
||||||
|
transition: transform 0.16s ease, box-shadow 0.16s ease, opacity 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-button:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 12px 28px rgba(240, 123, 45, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-message {
|
||||||
|
min-height: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-message.success {
|
||||||
|
color: #1f8f52;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-message.error {
|
||||||
|
color: #d13d38;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-output {
|
||||||
|
min-height: 420px;
|
||||||
|
max-height: 620px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid #dce7f5;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0)),
|
||||||
|
#102132;
|
||||||
|
color: #d5e5f6;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "JetBrains Mono", "Consolas", "Menlo", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 860px) {
|
||||||
|
.pp-log-hero,
|
||||||
|
.pp-log-toolbar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-summary {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.pp-log-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-page {
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pp-log-hero,
|
||||||
|
.pp-log-panel {
|
||||||
|
padding-left: 18px;
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="pp-log-page">
|
||||||
|
<div class="pp-log-hero">
|
||||||
|
<div class="pp-log-copy">
|
||||||
|
<div class="pp-log-kicker">OpenWrt Status</div>
|
||||||
|
<h2 class="pp-log-title">运行日志</h2>
|
||||||
|
<p class="pp-log-subtitle">这里会持续记录监控服务的启动、停止、单次丢包、恢复事件,以及国外代理 curl 探测失败时的原因,便于快速排查最近一次异常发生的时间和内容。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pp-log-summary">
|
||||||
|
<span class="pp-log-summary-label">日志说明</span>
|
||||||
|
<div class="pp-log-summary-value">默认保留最近 500 行事件记录,适合排查服务重启、连续失败、单次丢包、代理配置问题和恢复过程。</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pp-log-grid">
|
||||||
|
<div class="pp-log-meta">
|
||||||
|
<span class="pp-log-meta-label">最后更新时间</span>
|
||||||
|
<div class="pp-log-meta-value" id="logUpdatedAt">--</div>
|
||||||
|
</div>
|
||||||
|
<div class="pp-log-meta">
|
||||||
|
<span class="pp-log-meta-label">日志行数</span>
|
||||||
|
<div class="pp-log-meta-value" id="logLineCount">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="pp-log-meta">
|
||||||
|
<span class="pp-log-meta-label">日志大小</span>
|
||||||
|
<div class="pp-log-meta-value" id="logSize">0 B</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pp-log-panel">
|
||||||
|
<div class="pp-log-toolbar">
|
||||||
|
<div class="pp-log-tools">
|
||||||
|
<label class="pp-log-refresh">
|
||||||
|
<input type="checkbox" id="autoRefresh" checked>
|
||||||
|
<span>自动刷新</span>
|
||||||
|
</label>
|
||||||
|
<button class="pp-log-button" id="clearLogsButton" type="button">清除日志</button>
|
||||||
|
<span class="pp-log-message" id="logMessage"></span>
|
||||||
|
</div>
|
||||||
|
<div class="pp-log-note">取消勾选后会停止自动轮询,当前页面内容会保持不变。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre class="pp-log-output" id="logContent">正在加载日志...</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var apiBase = '<%=luci.dispatcher.build_url("admin", "status", "pingpacket")%>';
|
||||||
|
var token = (window.L && L.env && L.env.token) ? L.env.token : '<%=token%>';
|
||||||
|
var logsUrl = apiBase + '/get_logs';
|
||||||
|
var clearUrl = apiBase + '/clear_logs';
|
||||||
|
var refreshTimer = null;
|
||||||
|
var fetchInFlight = false;
|
||||||
|
var clearInFlight = false;
|
||||||
|
var refreshInterval = 2000;
|
||||||
|
|
||||||
|
function $(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMessage(text, type) {
|
||||||
|
var node = $('logMessage');
|
||||||
|
node.textContent = text || '';
|
||||||
|
node.className = type ? 'pp-log-message ' + type : 'pp-log-message';
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleRefresh(delay) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$('autoRefresh').checked) {
|
||||||
|
refreshTimer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshTimer = setTimeout(fetchLogs, typeof delay === 'number' ? delay : refreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(size) {
|
||||||
|
var value = Number(size) || 0;
|
||||||
|
if (value < 1024) {
|
||||||
|
return value + ' B';
|
||||||
|
}
|
||||||
|
if (value < 1024 * 1024) {
|
||||||
|
return (value / 1024).toFixed(1) + ' KB';
|
||||||
|
}
|
||||||
|
return (value / (1024 * 1024)).toFixed(1) + ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
function countLines(content) {
|
||||||
|
var normalized;
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized = content.replace(/\n+$/, '');
|
||||||
|
if (!normalized) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.split('\n').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateView(payload) {
|
||||||
|
var content = payload.content || '';
|
||||||
|
var output = $('logContent');
|
||||||
|
|
||||||
|
output.textContent = content || '当前暂无日志记录。';
|
||||||
|
$('logUpdatedAt').textContent = payload.updated_at || '--';
|
||||||
|
$('logLineCount').textContent = String(countLines(content));
|
||||||
|
$('logSize').textContent = formatBytes(payload.size || 0);
|
||||||
|
if ($('autoRefresh').checked) {
|
||||||
|
output.scrollTop = output.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchLogs() {
|
||||||
|
var xhr;
|
||||||
|
|
||||||
|
if (fetchInFlight || clearInFlight) {
|
||||||
|
scheduleRefresh(refreshInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchInFlight = true;
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', logsUrl, true);
|
||||||
|
xhr.onload = function() {
|
||||||
|
var response = null;
|
||||||
|
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
response = JSON.parse(xhr.responseText);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
updateView(response);
|
||||||
|
} else {
|
||||||
|
setMessage('日志读取失败,请稍后重试。', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = function() {
|
||||||
|
setMessage('日志读取失败,请稍后重试。', 'error');
|
||||||
|
};
|
||||||
|
xhr.onloadend = function() {
|
||||||
|
fetchInFlight = false;
|
||||||
|
scheduleRefresh(refreshInterval);
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLogs() {
|
||||||
|
var xhr;
|
||||||
|
|
||||||
|
if (fetchInFlight || clearInFlight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInFlight = true;
|
||||||
|
$('clearLogsButton').disabled = true;
|
||||||
|
setMessage('正在清除日志...', '');
|
||||||
|
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', clearUrl, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
xhr.onload = function() {
|
||||||
|
var response = null;
|
||||||
|
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
response = JSON.parse(xhr.responseText);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
updateView({
|
||||||
|
content: '',
|
||||||
|
updated_at: '',
|
||||||
|
size: 0
|
||||||
|
});
|
||||||
|
setMessage('日志已清除。', 'success');
|
||||||
|
} else {
|
||||||
|
setMessage('清除日志失败,请稍后重试。', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = function() {
|
||||||
|
setMessage('清除日志失败,请稍后重试。', 'error');
|
||||||
|
};
|
||||||
|
xhr.onloadend = function() {
|
||||||
|
clearInFlight = false;
|
||||||
|
$('clearLogsButton').disabled = false;
|
||||||
|
if ($('autoRefresh').checked) {
|
||||||
|
scheduleRefresh(refreshInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send('token=' + encodeURIComponent(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
$('autoRefresh').addEventListener('change', function() {
|
||||||
|
if (this.checked) {
|
||||||
|
setMessage('已恢复自动刷新。', 'success');
|
||||||
|
fetchLogs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage('已暂停自动刷新。', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('clearLogsButton').addEventListener('click', clearLogs);
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (!document.hidden && $('autoRefresh').checked) {
|
||||||
|
fetchLogs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('focus', function() {
|
||||||
|
if ($('autoRefresh').checked) {
|
||||||
|
fetchLogs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchLogs();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<%+footer%>
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -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 "服务状态更新失败。"
|
||||||
@@ -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'
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"luci-app-pingpacket": {
|
||||||
|
"description": "Grant access to luci-app-pingpacket",
|
||||||
|
"read": {
|
||||||
|
"uci": [
|
||||||
|
"pingpacket"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [
|
||||||
|
"pingpacket"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=Web UI for QEMU Virtual Machine Simple (VMS)
|
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_LICENSE:=GPLv3
|
||||||
PKG_VERSION:=0.0.1
|
PKG_VERSION:=0.0.1
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=qemu-vms
|
PKG_NAME:=qemu-vms
|
||||||
PKG_VERSION:=0.0.1
|
PKG_VERSION:=0.0.1
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/package.mk
|
|||||||
define Package/$(PKG_NAME)
|
define Package/$(PKG_NAME)
|
||||||
SECTION:=utils
|
SECTION:=utils
|
||||||
CATEGORY:=Utilities
|
CATEGORY:=Utilities
|
||||||
PKGARCH:=all
|
|
||||||
TITLE:=QEMU Virtual Machines Simple (VMS)
|
TITLE:=QEMU Virtual Machines Simple (VMS)
|
||||||
DEPENDS:= +qemu-x86_64-softmmu \
|
DEPENDS:= @TARGET_x86_64 \
|
||||||
+qemu-bridge-helper \
|
+qemu-x86_64-softmmu \
|
||||||
+minicom \
|
+qemu-bridge-helper \
|
||||||
+socat
|
+minicom \
|
||||||
|
+socat
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/$(PKG_NAME)/description
|
define Package/$(PKG_NAME)/description
|
||||||
|
|||||||
Reference in new issue
Block a user