├── .github ├── build-ipk.sh └── workflows │ ├── build-ipk.yml │ └── rescan-translation.yml ├── LICENSE ├── Makefile ├── README ├── htdocs └── luci-static │ └── resources │ ├── homeproxy.js │ └── view │ └── homeproxy │ ├── client.js │ ├── node.js │ ├── server.js │ └── status.js ├── po ├── templates │ └── homeproxy.pot └── zh_Hans │ └── homeproxy.po └── root ├── etc ├── capabilities │ └── homeproxy.json ├── config │ └── homeproxy ├── homeproxy │ ├── resources │ │ ├── china_ip4.txt │ │ ├── china_ip4.ver │ │ ├── china_ip6.txt │ │ ├── china_ip6.ver │ │ ├── china_list.txt │ │ ├── china_list.ver │ │ ├── gfw_list.txt │ │ └── gfw_list.ver │ └── scripts │ │ ├── clean_log.sh │ │ ├── firewall_post.ut │ │ ├── firewall_pre.ut │ │ ├── generate_client.uc │ │ ├── generate_server.uc │ │ ├── homeproxy.uc │ │ ├── update_crond.sh │ │ ├── update_resources.sh │ │ └── update_subscriptions.uc ├── init.d │ └── homeproxy └── uci-defaults │ ├── luci-homeproxy │ └── luci-homeproxy-migration └── usr └── share ├── luci └── menu.d │ └── luci-app-homeproxy.json └── rpcd ├── acl.d └── luci-app-homeproxy.json └── ucode └── luci.homeproxy /.github/build-ipk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # 4 | # Copyright (C) 2023 Tianling Shen 5 | 6 | set -o errexit 7 | set -o pipefail 8 | 9 | export PKG_SOURCE_DATE_EPOCH="$(date "+%s")" 10 | 11 | BASE_DIR="$(cd "$(dirname $0)"; pwd)" 12 | PKG_DIR="$BASE_DIR/.." 13 | 14 | function get_mk_value() { 15 | awk -F "$1:=" '{print $2}' "$PKG_DIR/Makefile" | xargs 16 | } 17 | 18 | PKG_NAME="$(get_mk_value "PKG_NAME")" 19 | if [ "$RELEASE_TYPE" == "release" ]; then 20 | PKG_VERSION="$(get_mk_value "PKG_VERSION")" 21 | else 22 | PKG_VERSION="dev-$PKG_SOURCE_DATE_EPOCH-$(git rev-parse --short HEAD)" 23 | fi 24 | 25 | TEMP_DIR="$(mktemp -d -p $BASE_DIR)" 26 | TEMP_PKG_DIR="$TEMP_DIR/$PKG_NAME" 27 | mkdir -p "$TEMP_PKG_DIR/CONTROL/" 28 | mkdir -p "$TEMP_PKG_DIR/lib/upgrade/keep.d/" 29 | mkdir -p "$TEMP_PKG_DIR/usr/lib/lua/luci/i18n/" 30 | mkdir -p "$TEMP_PKG_DIR/www/" 31 | 32 | cp -fpR "$PKG_DIR/htdocs"/* "$TEMP_PKG_DIR/www/" 33 | cp -fpR "$PKG_DIR/root"/* "$TEMP_PKG_DIR/" 34 | 35 | echo -e "/etc/config/homeproxy" > "$TEMP_PKG_DIR/CONTROL/conffiles" 36 | cat > "$TEMP_PKG_DIR/lib/upgrade/keep.d/$PKG_NAME" <<-EOF 37 | /etc/homeproxy/certs/ 38 | /etc/homeproxy/ruleset/ 39 | /etc/homeproxy/resources/direct_list.txt 40 | /etc/homeproxy/resources/proxy_list.txt 41 | EOF 42 | 43 | cat > "$TEMP_PKG_DIR/CONTROL/control" <<-EOF 44 | Package: $PKG_NAME 45 | Version: $PKG_VERSION 46 | Depends: libc, sing-box, chinadns-ng, firewall4, kmod-nft-tproxy 47 | Source: https://github.com/immortalwrt/homeproxy 48 | SourceName: $PKG_NAME 49 | Section: luci 50 | SourceDateEpoch: $PKG_SOURCE_DATE_EPOCH 51 | Maintainer: Tianling Shen 52 | Architecture: all 53 | Installed-Size: TO-BE-FILLED-BY-IPKG-BUILD 54 | Description: The modern ImmortalWrt proxy platform for ARM64/AMD64 55 | EOF 56 | 57 | git clone --filter=blob:none --no-checkout "https://github.com/openwrt/luci.git" "po2lmo" 58 | pushd "po2lmo" 59 | git config core.sparseCheckout true 60 | echo "modules/luci-base/src" >> ".git/info/sparse-checkout" 61 | git checkout 62 | cd "modules/luci-base/src" 63 | make po2lmo 64 | ./po2lmo "$PKG_DIR/po/zh_Hans/homeproxy.po" "$TEMP_PKG_DIR/usr/lib/lua/luci/i18n/homeproxy.zh-cn.lmo" 65 | popd 66 | rm -rf "po2lmo" 67 | 68 | echo -e '#!/bin/sh 69 | [ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0 70 | [ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0 71 | . ${IPKG_INSTROOT}/lib/functions.sh 72 | default_postinst $0 $@' > "$TEMP_PKG_DIR/CONTROL/postinst" 73 | chmod 0755 "$TEMP_PKG_DIR/CONTROL/postinst" 74 | 75 | echo -e "[ -n "\${IPKG_INSTROOT}" ] || { 76 | (. /etc/uci-defaults/$PKG_NAME) && rm -f /etc/uci-defaults/$PKG_NAME 77 | rm -f /tmp/luci-indexcache 78 | rm -rf /tmp/luci-modulecache/ 79 | exit 0 80 | }" > "$TEMP_PKG_DIR/CONTROL/postinst-pkg" 81 | chmod 0755 "$TEMP_PKG_DIR/CONTROL/postinst-pkg" 82 | 83 | echo -e '#!/bin/sh 84 | [ -s ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0 85 | . ${IPKG_INSTROOT}/lib/functions.sh 86 | default_prerm $0 $@' > "$TEMP_PKG_DIR/CONTROL/prerm" 87 | chmod 0755 "$TEMP_PKG_DIR/CONTROL/prerm" 88 | 89 | curl -fsSL "https://raw.githubusercontent.com/openwrt/openwrt/master/scripts/ipkg-build" -o "$TEMP_DIR/ipkg-build" 90 | chmod 0755 "$TEMP_DIR/ipkg-build" 91 | "$TEMP_DIR/ipkg-build" -m "" "$TEMP_PKG_DIR" "$TEMP_DIR" 92 | 93 | mv "$TEMP_DIR/${PKG_NAME}_${PKG_VERSION}_all.ipk" "$BASE_DIR/${PKG_NAME}_${PKG_VERSION}_all.ipk" 94 | rm -rf "$TEMP_DIR" 95 | -------------------------------------------------------------------------------- /.github/workflows/build-ipk.yml: -------------------------------------------------------------------------------- 1 | name: Build ipk for HomeProxy 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'dev' 8 | paths: 9 | - 'htdocs/**' 10 | - 'po/**' 11 | - 'root/**' 12 | - 'Makefile' 13 | - '.github/**' 14 | 15 | pull_request: 16 | branches: 17 | - 'master' 18 | - 'dev' 19 | types: 20 | - opened 21 | - synchronize 22 | - reopened 23 | paths: 24 | - 'htdocs/**' 25 | - 'root/**' 26 | - 'Makefile' 27 | - '.github/**' 28 | 29 | release: 30 | types: 31 | - published 32 | 33 | jobs: 34 | build: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - name: Checkout source tree 39 | uses: actions/checkout@v4 40 | 41 | - name: Build ipk file 42 | env: 43 | RELEASE_TYPE: ${{ github.event_name }} 44 | run: | 45 | pushd .github 46 | fakeroot bash build-ipk.sh 47 | echo "ASSET_NAME=$(ls *.ipk)" >> $GITHUB_ENV 48 | popd 49 | 50 | - name: Publishing to GitHub Artifacts 51 | uses: actions/upload-artifact@v4 52 | if: github.event_name != 'release' 53 | with: 54 | name: ${{ env.ASSET_NAME }} 55 | path: .github/*.ipk 56 | 57 | - name: Publishing to GitHub Releases 58 | uses: svenstaro/upload-release-action@v2 59 | if: github.event_name == 'release' 60 | with: 61 | repo_token: ${{ github.token }} 62 | file: .github/*.ipk 63 | tag: ${{ github.ref }} 64 | file_glob: true 65 | -------------------------------------------------------------------------------- /.github/workflows/rescan-translation.yml: -------------------------------------------------------------------------------- 1 | name: Rescan translation 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | paths: 8 | - 'htdocs/**' 9 | - 'root/**' 10 | 11 | jobs: 12 | scan: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | 17 | steps: 18 | - name: Checkout source tree 19 | uses: actions/checkout@v4 20 | 21 | - name: Rescan translation 22 | env: 23 | DEBIAN_FRONTEND: noninteractive 24 | run: | 25 | sudo -E apt-get -y install gettext 26 | curl -LO "https://github.com/openwrt/luci/raw/master/build/i18n-scan.pl" 27 | curl -LO "https://github.com/openwrt/luci/raw/master/build/i18n-update.pl" 28 | perl "i18n-scan.pl" . > "po/templates/homeproxy.pot" 29 | perl "i18n-update.pl" "po" 30 | find po/ -name '*.po~' -exec rm -f {} \; 31 | rm -f "i18n-scan.pl" "i18n-update.pl" 32 | [ -z "$(git status -s)" ] || echo -e "CHANGE_STAT=1" >> "$GITHUB_ENV" 33 | 34 | - name: Commit changes 35 | if: ${{ env.CHANGE_STAT }} 36 | run: | 37 | git config --local user.name "github-actions[bot]" 38 | git config --local user.email "<41898282+github-actions[bot]@users.noreply.github.com>" 39 | git add . 40 | git commit -m "chore(po): rescan translation" 41 | git push -f origin HEAD:po 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | # 3 | # Copyright (C) 2022-2023 ImmortalWrt.org 4 | 5 | include $(TOPDIR)/rules.mk 6 | 7 | LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64 8 | LUCI_PKGARCH:=all 9 | LUCI_DEPENDS:= \ 10 | +sing-box \ 11 | +chinadns-ng \ 12 | +firewall4 \ 13 | +kmod-nft-tproxy 14 | 15 | PKG_NAME:=luci-app-homeproxy 16 | 17 | define Package/luci-app-homeproxy/conffiles 18 | /etc/config/homeproxy 19 | /etc/homeproxy/certs/ 20 | /etc/homeproxy/ruleset/ 21 | /etc/homeproxy/resources/direct_list.txt 22 | /etc/homeproxy/resources/proxy_list.txt 23 | /etc/homeproxy/cache.db 24 | endef 25 | 26 | include $(TOPDIR)/feeds/luci/luci.mk 27 | 28 | # call BuildPackage - OpenWrt buildroot signature 29 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | TODO: 2 | - Subscription page slow response with a large number of nodes 3 | - Refactor nft rules 4 | - Support Clash selector, urltest etc. 5 | - Move ACL settings to a dedicated page 6 | - Any other improvements 7 | -------------------------------------------------------------------------------- /htdocs/luci-static/resources/homeproxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0-only 3 | * 4 | * Copyright (C) 2022-2023 ImmortalWrt.org 5 | */ 6 | 7 | 'use strict'; 8 | 'require baseclass'; 9 | 'require form'; 10 | 'require fs'; 11 | 'require rpc'; 12 | 'require uci'; 13 | 'require ui'; 14 | 15 | return baseclass.extend({ 16 | dns_strategy: { 17 | '': _('Default'), 18 | 'prefer_ipv4': _('Prefer IPv4'), 19 | 'prefer_ipv6': _('Prefer IPv6'), 20 | 'ipv4_only': _('IPv4 only'), 21 | 'ipv6_only': _('IPv6 only') 22 | }, 23 | 24 | shadowsocks_encrypt_methods: [ 25 | /* Stream */ 26 | 'none', 27 | /* AEAD */ 28 | 'aes-128-gcm', 29 | 'aes-192-gcm', 30 | 'aes-256-gcm', 31 | 'chacha20-ietf-poly1305', 32 | 'xchacha20-ietf-poly1305', 33 | /* AEAD 2022 */ 34 | '2022-blake3-aes-128-gcm', 35 | '2022-blake3-aes-256-gcm', 36 | '2022-blake3-chacha20-poly1305' 37 | ], 38 | 39 | tls_cipher_suites: [ 40 | 'TLS_RSA_WITH_AES_128_CBC_SHA', 41 | 'TLS_RSA_WITH_AES_256_CBC_SHA', 42 | 'TLS_RSA_WITH_AES_128_GCM_SHA256', 43 | 'TLS_RSA_WITH_AES_256_GCM_SHA384', 44 | 'TLS_AES_128_GCM_SHA256', 45 | 'TLS_AES_256_GCM_SHA384', 46 | 'TLS_CHACHA20_POLY1305_SHA256', 47 | 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA', 48 | 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA', 49 | 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', 50 | 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', 51 | 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 52 | 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', 53 | 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256', 54 | 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', 55 | 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 56 | 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' 57 | ], 58 | 59 | tls_versions: [ 60 | '1.0', 61 | '1.1', 62 | '1.2', 63 | '1.3' 64 | ], 65 | 66 | calcStringMD5: function(e) { 67 | /* Thanks to https://stackoverflow.com/a/41602636 */ 68 | function h(a, b) { 69 | var c, d, e, f, g; 70 | e = a & 2147483648; 71 | f = b & 2147483648; 72 | c = a & 1073741824; 73 | d = b & 1073741824; 74 | g = (a & 1073741823) + (b & 1073741823); 75 | return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f; 76 | } 77 | function k(a, b, c, d, e, f, g) { a = h(a, h(h(b & c | ~b & d, e), g)); return h(a << f | a >>> 32 - f, b); } 78 | function l(a, b, c, d, e, f, g) { a = h(a, h(h(b & d | c & ~d, e), g)); return h(a << f | a >>> 32 - f, b); } 79 | function m(a, b, d, c, e, f, g) { a = h(a, h(h(b ^ d ^ c, e), g)); return h(a << f | a >>> 32 - f, b); } 80 | function n(a, b, d, c, e, f, g) { a = h(a, h(h(d ^ (b | ~c), e), g)); return h(a << f | a >>> 32 - f, b); } 81 | function p(a) { 82 | var b = '', d = ''; 83 | for (var c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = '0' + d.toString(16), b += d.substr(d.length - 2, 2); 84 | return b; 85 | } 86 | 87 | var f = [], q, r, s, t, a, b, c, d; 88 | e = function(a) { 89 | a = a.replace(/\r\n/g, '\n'); 90 | for (var b = '', d = 0; d < a.length; d++) { 91 | var c = a.charCodeAt(d); 92 | 128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) : 93 | (b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)), 94 | b += String.fromCharCode(c & 63 | 128)) 95 | } 96 | return b; 97 | }(e); 98 | f = function(b) { 99 | var c = b.length, a = c + 8; 100 | for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;) 101 | a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++; 102 | a = (g - g % 4) / 4; e[a] |= 128 << g % 4 * 8; e[d - 2] = c << 3; e[d - 1] = c >>> 29; 103 | return e; 104 | }(e); 105 | a = 1732584193; 106 | b = 4023233417; 107 | c = 2562383102; 108 | d = 271733878; 109 | 110 | for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d, 111 | a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710), 112 | c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966), 113 | a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426), 114 | c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313), 115 | a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879), 116 | c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134), 117 | a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195), 118 | c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329), 119 | a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664), 120 | c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994), 121 | a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083), 122 | c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448), 123 | a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606), 124 | c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501), 125 | a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512), 126 | c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562), 127 | a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833), 128 | c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740), 129 | a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353), 130 | c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656), 131 | a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074), 132 | c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189), 133 | a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461), 134 | c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645), 135 | a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415), 136 | c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241), 137 | a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690), 138 | c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497), 139 | a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552), 140 | c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649), 141 | a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917), 142 | c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745), 143 | a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t); 144 | return (p(a) + p(b) + p(c) + p(d)).toLowerCase(); 145 | }, 146 | 147 | decodeBase64Str: function(str) { 148 | if (!str) 149 | return null; 150 | 151 | /* Thanks to luci-app-ssr-plus */ 152 | str = str.replace(/-/g, '+').replace(/_/g, '/'); 153 | var padding = (4 - str.length % 4) % 4; 154 | if (padding) 155 | str = str + Array(padding + 1).join('='); 156 | 157 | return decodeURIComponent(Array.prototype.map.call(atob(str), (c) => 158 | '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) 159 | ).join('')); 160 | }, 161 | 162 | getBuiltinFeatures: function() { 163 | var callGetSingBoxFeatures = rpc.declare({ 164 | object: 'luci.homeproxy', 165 | method: 'singbox_get_features', 166 | expect: { '': {} } 167 | }); 168 | 169 | return L.resolveDefault(callGetSingBoxFeatures(), {}); 170 | }, 171 | 172 | generateUUIDv4: function() { 173 | /* Thanks to https://stackoverflow.com/a/2117523 */ 174 | return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => 175 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 176 | ); 177 | }, 178 | 179 | loadDefaultLabel: function(uciconfig, ucisection) { 180 | var label = uci.get(uciconfig, ucisection, 'label'); 181 | if (label) { 182 | return label; 183 | } else { 184 | uci.set(uciconfig, ucisection, 'label', ucisection); 185 | return ucisection; 186 | } 187 | }, 188 | 189 | loadModalTitle: function(title, addtitle, uciconfig, ucisection) { 190 | var label = uci.get(uciconfig, ucisection, 'label'); 191 | return label ? title + ' » ' + label : addtitle; 192 | }, 193 | 194 | renderSectionAdd: function(section, extra_class) { 195 | var el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]), 196 | nameEl = el.querySelector('.cbi-section-create-name'); 197 | ui.addValidator(nameEl, 'uciname', true, (v) => { 198 | var button = el.querySelector('.cbi-section-create > .cbi-button-add'); 199 | var uciconfig = section.uciconfig || section.map.config; 200 | 201 | if (!v) { 202 | button.disabled = true; 203 | return true; 204 | } else if (uci.get(uciconfig, v)) { 205 | button.disabled = true; 206 | return _('Expecting: %s').format(_('unique UCI identifier')); 207 | } else { 208 | button.disabled = null; 209 | return true; 210 | } 211 | }, 'blur', 'keyup'); 212 | 213 | return el; 214 | }, 215 | 216 | uploadCertificate: function(option, type, filename, ev) { 217 | var callWriteCertificate = rpc.declare({ 218 | object: 'luci.homeproxy', 219 | method: 'certificate_write', 220 | params: ['filename'], 221 | expect: { '': {} } 222 | }); 223 | 224 | return ui.uploadFile('/tmp/homeproxy_certificate.tmp', ev.target) 225 | .then(L.bind((btn, res) => { 226 | return L.resolveDefault(callWriteCertificate(filename), {}).then((ret) => { 227 | if (ret.result === true) 228 | ui.addNotification(null, E('p', _('Your %s was successfully uploaded. Size: %sB.').format(type, res.size))); 229 | else 230 | ui.addNotification(null, E('p', _('Failed to upload %s, error: %s.').format(type, ret.error))); 231 | }); 232 | }, this, ev.target)) 233 | .catch((e) => { ui.addNotification(null, E('p', e.message)) }); 234 | }, 235 | 236 | validateBase64Key: function(length, section_id, value) { 237 | /* Thanks to luci-proto-wireguard */ 238 | if (section_id && value) 239 | if (value.length !== length || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) || value[length-1] !== '=') 240 | return _('Expecting: %s').format(_('valid base64 key with %d characters').format(length)); 241 | 242 | return true; 243 | }, 244 | 245 | validateUniqueValue: function(uciconfig, ucisection, ucioption, section_id, value) { 246 | if (section_id) { 247 | if (!value) 248 | return _('Expecting: %s').format(_('non-empty value')); 249 | 250 | var duplicate = false; 251 | uci.sections(uciconfig, ucisection, (res) => { 252 | if (res['.name'] !== section_id) 253 | if (res[ucioption] === value) 254 | duplicate = true 255 | }); 256 | if (duplicate) 257 | return _('Expecting: %s').format(_('unique value')); 258 | } 259 | 260 | return true; 261 | }, 262 | 263 | validateUUID: function(section_id, value) { 264 | if (section_id) { 265 | if (!value) 266 | return _('Expecting: %s').format(_('non-empty value')); 267 | else if (value.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null) 268 | return _('Expecting: %s').format(_('valid uuid')); 269 | } 270 | 271 | return true; 272 | } 273 | }); 274 | -------------------------------------------------------------------------------- /htdocs/luci-static/resources/view/homeproxy/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0-only 3 | * 4 | * Copyright (C) 2022-2023 ImmortalWrt.org 5 | */ 6 | 7 | 'use strict'; 8 | 'require form'; 9 | 'require poll'; 10 | 'require rpc'; 11 | 'require uci'; 12 | 'require view'; 13 | 14 | 'require homeproxy as hp'; 15 | 16 | var callServiceList = rpc.declare({ 17 | object: 'service', 18 | method: 'list', 19 | params: ['name'], 20 | expect: { '': {} } 21 | }); 22 | 23 | function getServiceStatus() { 24 | return L.resolveDefault(callServiceList('homeproxy'), {}).then((res) => { 25 | var isRunning = false; 26 | try { 27 | isRunning = res['homeproxy']['instances']['sing-box-s']['running']; 28 | } catch (e) { } 29 | return isRunning; 30 | }); 31 | } 32 | 33 | function renderStatus(isRunning) { 34 | var spanTemp = '%s %s'; 35 | var renderHTML; 36 | if (isRunning) 37 | renderHTML = spanTemp.format('green', _('HomeProxy Server'), _('RUNNING')); 38 | else 39 | renderHTML = spanTemp.format('red', _('HomeProxy Server'), _('NOT RUNNING')); 40 | 41 | return renderHTML; 42 | } 43 | 44 | return view.extend({ 45 | load: function() { 46 | return Promise.all([ 47 | uci.load('homeproxy'), 48 | hp.getBuiltinFeatures() 49 | ]); 50 | }, 51 | 52 | render: function(data) { 53 | var m, s, o; 54 | var features = data[1]; 55 | 56 | m = new form.Map('homeproxy', _('HomeProxy Server'), 57 | _('The modern ImmortalWrt proxy platform for ARM64/AMD64.')); 58 | 59 | s = m.section(form.TypedSection); 60 | s.render = function () { 61 | poll.add(function () { 62 | return L.resolveDefault(getServiceStatus()).then((res) => { 63 | var view = document.getElementById('service_status'); 64 | view.innerHTML = renderStatus(res); 65 | }); 66 | }); 67 | 68 | return E('div', { class: 'cbi-section', id: 'status_bar' }, [ 69 | E('p', { id: 'service_status' }, _('Collecting data...')) 70 | ]); 71 | } 72 | 73 | s = m.section(form.NamedSection, 'server', 'homeproxy', _('Global settings')); 74 | 75 | o = s.option(form.Flag, 'enabled', _('Enable')); 76 | o.default = o.disabled; 77 | o.rmempty = false; 78 | 79 | o = s.option(form.Flag, 'auto_firewall', _('Auto configure firewall')); 80 | o.default = o.disabled; 81 | o.rmempty = false; 82 | 83 | s = m.section(form.GridSection, 'server', _('Server settings')); 84 | s.addremove = true; 85 | s.rowcolors = true; 86 | s.sortable = true; 87 | s.nodescriptions = true; 88 | s.modaltitle = L.bind(hp.loadModalTitle, this, _('Server'), _('Add a server'), data[0]); 89 | s.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); 90 | s.renderSectionAdd = L.bind(hp.renderSectionAdd, this, s); 91 | 92 | o = s.option(form.Value, 'label', _('Label')); 93 | o.load = L.bind(hp.loadDefaultLabel, this, data[0]); 94 | o.validate = L.bind(hp.validateUniqueValue, this, data[0], 'server', 'label'); 95 | o.rmempty = false; 96 | o.modalonly = true; 97 | 98 | o = s.option(form.Flag, 'enabled', _('Enable')); 99 | o.default = o.enabled; 100 | o.rmempty = false; 101 | o.editable = true; 102 | 103 | o = s.option(form.ListValue, 'type', _('Type')); 104 | o.value('http', _('HTTP')); 105 | if (features.with_quic) { 106 | o.value('hysteria', _('Hysteria')); 107 | o.value('hysteria2', _('Hysteria2')); 108 | o.value('naive', _('NaïveProxy')); 109 | } 110 | o.value('shadowsocks', _('Shadowsocks')); 111 | o.value('socks', _('Socks')); 112 | o.value('trojan', _('Trojan')); 113 | if (features.with_quic) 114 | o.value('tuic', _('Tuic')); 115 | o.value('vless', _('VLESS')); 116 | o.value('vmess', _('VMess')); 117 | o.rmempty = false; 118 | 119 | o = s.option(form.Value, 'address', _('Listen address')); 120 | o.placeholder = '::'; 121 | o.datatype = 'ipaddr'; 122 | o.modalonly = true; 123 | 124 | o = s.option(form.Value, 'port', _('Listen port'), 125 | _('The port must be unique.')); 126 | o.datatype = 'port'; 127 | o.validate = L.bind(hp.validateUniqueValue, this, data[0], 'server', 'port'); 128 | 129 | o = s.option(form.Value, 'username', _('Username')); 130 | o.depends('type', 'http'); 131 | o.depends('type', 'naive'); 132 | o.depends('type', 'socks'); 133 | o.modalonly = true; 134 | 135 | o = s.option(form.Value, 'password', _('Password')); 136 | o.password = true; 137 | o.depends({'type': /^(http|naive|socks)$/, 'username': /[\s\S]/}); 138 | o.depends('type', 'hysteria2'); 139 | o.depends('type', 'shadowsocks'); 140 | o.depends('type', 'trojan'); 141 | o.depends('type', 'tuic'); 142 | o.validate = function(section_id, value) { 143 | if (section_id) { 144 | var type = this.map.lookupOption('type', section_id)[0].formvalue(section_id); 145 | var required_type = [ 'http', 'naive', 'socks', 'shadowsocks' ]; 146 | 147 | if (required_type.includes(type)) { 148 | if (type === 'shadowsocks') { 149 | var encmode = this.map.lookupOption('shadowsocks_encrypt_method', section_id)[0].formvalue(section_id); 150 | if (encmode === 'none') 151 | return true; 152 | else if (encmode === '2022-blake3-aes-128-gcm') 153 | return hp.validateBase64Key(24, section_id, value); 154 | else if (['2022-blake3-aes-256-gcm', '2022-blake3-chacha20-poly1305'].includes(encmode)) 155 | return hp.validateBase64Key(44, section_id, value); 156 | } 157 | 158 | if (!value) 159 | return _('Expecting: %s').format(_('non-empty value')); 160 | } 161 | } 162 | 163 | return true; 164 | } 165 | o.modalonly = true; 166 | 167 | /* Hysteria (2) config start */ 168 | o = s.option(form.ListValue, 'hysteria_protocol', _('Protocol')); 169 | o.value('udp'); 170 | /* WeChat-Video / FakeTCP are unsupported by sing-box currently 171 | o.value('wechat-video'); 172 | o.value('faketcp'); 173 | */ 174 | o.default = 'udp'; 175 | o.depends('type', 'hysteria'); 176 | o.rmempty = false; 177 | o.modalonly = true; 178 | 179 | o = s.option(form.Value, 'hysteria_down_mbps', _('Max download speed'), 180 | _('Max download speed in Mbps.')); 181 | o.datatype = 'uinteger'; 182 | o.depends('type', 'hysteria'); 183 | o.depends('type', 'hysteria2'); 184 | o.modalonly = true; 185 | 186 | o = s.option(form.Value, 'hysteria_up_mbps', _('Max upload speed'), 187 | _('Max upload speed in Mbps.')); 188 | o.datatype = 'uinteger'; 189 | o.depends('type', 'hysteria'); 190 | o.depends('type', 'hysteria2'); 191 | o.modalonly = true; 192 | 193 | o = s.option(form.ListValue, 'hysteria_auth_type', _('Authentication type')); 194 | o.value('', _('Disable')); 195 | o.value('base64', _('Base64')); 196 | o.value('string', _('String')); 197 | o.depends('type', 'hysteria'); 198 | o.modalonly = true; 199 | 200 | o = s.option(form.Value, 'hysteria_auth_payload', _('Authentication payload')); 201 | o.depends({'type': 'hysteria', 'hysteria_auth_type': /[\s\S]/}); 202 | o.rmempty = false; 203 | o.modalonly = true; 204 | 205 | o = s.option(form.ListValue, 'hysteria_obfs_type', _('Obfuscate type')); 206 | o.value('', _('Disable')); 207 | o.value('salamander', _('Salamander')); 208 | o.depends('type', 'hysteria2'); 209 | o.modalonly = true; 210 | 211 | o = s.option(form.Value, 'hysteria_obfs_password', _('Obfuscate password')); 212 | o.depends('type', 'hysteria'); 213 | o.depends({'type': 'hysteria2', 'hysteria_obfs_type': /[\s\S]/}); 214 | o.modalonly = true; 215 | 216 | o = s.option(form.Value, 'hysteria_recv_window_conn', _('QUIC stream receive window'), 217 | _('The QUIC stream-level flow control window for receiving data.')); 218 | o.datatype = 'uinteger'; 219 | o.default = '67108864'; 220 | o.depends('type', 'hysteria'); 221 | o.modalonly = true; 222 | 223 | o = s.option(form.Value, 'hysteria_recv_window_client', _('QUIC connection receive window'), 224 | _('The QUIC connection-level flow control window for receiving data.')); 225 | o.datatype = 'uinteger'; 226 | o.default = '15728640'; 227 | o.depends('type', 'hysteria'); 228 | o.modalonly = true; 229 | 230 | o = s.option(form.Value, 'hysteria_max_conn_client', _('QUIC maximum concurrent bidirectional streams'), 231 | _('The maximum number of QUIC concurrent bidirectional streams that a peer is allowed to open.')); 232 | o.datatype = 'uinteger'; 233 | o.default = '1024'; 234 | o.depends('type', 'hysteria'); 235 | o.modalonly = true; 236 | 237 | o = s.option(form.Flag, 'hysteria_disable_mtu_discovery', _('Disable Path MTU discovery'), 238 | _('Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.')); 239 | o.default = o.disabled; 240 | o.depends('type', 'hysteria'); 241 | o.modalonly = true; 242 | 243 | o = s.option(form.Flag, 'hysteria_ignore_client_bandwidth', _('Ignore client bandwidth'), 244 | _('Tell the client to use the BBR flow control algorithm instead of Hysteria CC.')); 245 | o.default = o.disabled; 246 | o.depends({'type': 'hysteria2', 'hysteria_down_mbps': '', 'hysteria_up_mbps': ''}); 247 | o.modalonly = true; 248 | 249 | o = s.option(form.Value, 'hysteria_masquerade', _('Masquerade'), 250 | _('HTTP3 server behavior when authentication fails.
A 404 page will be returned if empty.')); 251 | o.depends('type', 'hysteria2'); 252 | o.modalonly = true; 253 | /* Hysteria (2) config end */ 254 | 255 | /* Shadowsocks config */ 256 | o = s.option(form.ListValue, 'shadowsocks_encrypt_method', _('Encrypt method')); 257 | for (var i of hp.shadowsocks_encrypt_methods) 258 | o.value(i); 259 | o.default = 'aes-128-gcm'; 260 | o.depends('type', 'shadowsocks'); 261 | o.modalonly = true; 262 | 263 | /* Tuic config start */ 264 | o = s.option(form.Value, 'uuid', _('UUID')); 265 | o.depends('type', 'tuic'); 266 | o.depends('type', 'vless'); 267 | o.depends('type', 'vmess'); 268 | o.validate = hp.validateUUID; 269 | o.modalonly = true; 270 | 271 | o = s.option(form.ListValue, 'tuic_congestion_control', _('Congestion control algorithm'), 272 | _('QUIC congestion control algorithm.')); 273 | o.value('cubic'); 274 | o.value('new_reno'); 275 | o.value('bbr'); 276 | o.default = 'cubic'; 277 | o.depends('type', 'tuic'); 278 | o.modalonly = true; 279 | 280 | o = s.option(form.ListValue, 'tuic_auth_timeout', _('Auth timeout'), 281 | _('How long the server should wait for the client to send the authentication command (in seconds).')); 282 | o.datatype = 'uinteger'; 283 | o.default = '3'; 284 | o.depends('type', 'tuic'); 285 | o.modalonly = true; 286 | 287 | o = s.option(form.Flag, 'tuic_enable_zero_rtt', _('Enable 0-RTT handshake'), 288 | _('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed.
' + 289 | 'Disabling this is highly recommended, as it is vulnerable to replay attacks.')); 290 | o.default = o.disabled; 291 | o.depends('type', 'tuic'); 292 | o.modalonly = true; 293 | 294 | o = s.option(form.Value, 'tuic_heartbeat', _('Heartbeat interval'), 295 | _('Interval for sending heartbeat packets for keeping the connection alive (in seconds).')); 296 | o.datatype = 'uinteger'; 297 | o.default = '10'; 298 | o.depends('type', 'tuic'); 299 | o.modalonly = true; 300 | /* Tuic config end */ 301 | 302 | /* VLESS / VMess config start */ 303 | o = s.option(form.ListValue, 'vless_flow', _('Flow')); 304 | o.value('', _('None')); 305 | o.value('xtls-rprx-vision'); 306 | o.depends('type', 'vless'); 307 | o.modalonly = true; 308 | 309 | o = s.option(form.Value, 'vmess_alterid', _('Alter ID'), 310 | _('Legacy protocol support (VMess MD5 Authentication) is provided for compatibility purposes only, use of alterId > 1 is not recommended.')); 311 | o.datatype = 'uinteger'; 312 | o.depends('type', 'vmess'); 313 | o.modalonly = true; 314 | /* VMess config end */ 315 | 316 | /* Transport config start */ 317 | o = s.option(form.ListValue, 'transport', _('Transport'), 318 | _('No TCP transport, plain HTTP is merged into the HTTP transport.')); 319 | o.value('', _('None')); 320 | o.value('grpc', _('gRPC')); 321 | o.value('http', _('HTTP')); 322 | o.value('httpupgrade', _('HTTPUpgrade')); 323 | o.value('quic', _('QUIC')); 324 | o.value('ws', _('WebSocket')); 325 | o.depends('type', 'trojan'); 326 | o.depends('type', 'vless'); 327 | o.depends('type', 'vmess'); 328 | o.onchange = function(ev, section_id, value) { 329 | var desc = this.map.findElement('id', 'cbid.homeproxy.%s.transport'.format(section_id)).nextElementSibling; 330 | if (value === 'http') 331 | desc.innerHTML = _('TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used.'); 332 | else if (value === 'quic') 333 | desc.innerHTML = _('No additional encryption support: It\'s basically duplicate encryption.'); 334 | else 335 | desc.innerHTML = _('No TCP transport, plain HTTP is merged into the HTTP transport.'); 336 | 337 | var tls_element = this.map.findElement('id', 'cbid.homeproxy.%s.tls'.format(section_id)).firstElementChild; 338 | if ((value === 'http' && tls_element.checked) || (value === 'grpc' && !features.with_grpc)) 339 | this.map.findElement('id', 'cbid.homeproxy.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML = 340 | _('Specifies the time (in seconds) until idle clients should be closed with a GOAWAY frame. PING frames are not considered as activity.'); 341 | else if (value === 'grpc' && features.with_grpc) 342 | this.map.findElement('id', 'cbid.homeproxy.%s.http_idle_timeout'.format(section_id)).nextElementSibling.innerHTML = 343 | _('If the transport doesn\'t see any activity after a duration of this time (in seconds), it pings the client to check if the connection is still active.'); 344 | } 345 | o.modalonly = true; 346 | 347 | /* gRPC config start */ 348 | o = s.option(form.Value, 'grpc_servicename', _('gRPC service name')); 349 | o.depends('transport', 'grpc'); 350 | o.modalonly = true; 351 | 352 | /* gRPC config end */ 353 | 354 | /* HTTP(Upgrade) config start */ 355 | o = s.option(form.DynamicList, 'http_host', _('Host')); 356 | o.datatype = 'hostname'; 357 | o.depends('transport', 'http'); 358 | o.modalonly = true; 359 | 360 | o = s.option(form.Value, 'httpupgrade_host', _('Host')); 361 | o.datatype = 'hostname'; 362 | o.depends('transport', 'httpupgrade'); 363 | o.modalonly = true; 364 | 365 | o = s.option(form.Value, 'http_path', _('Path')); 366 | o.depends('transport', 'http'); 367 | o.depends('transport', 'httpupgrade'); 368 | o.modalonly = true; 369 | 370 | o = s.option(form.Value, 'http_method', _('Method')); 371 | o.depends('transport', 'http'); 372 | o.modalonly = true; 373 | 374 | o = s.option(form.Value, 'http_idle_timeout', _('Idle timeout'), 375 | _('Specifies the time (in seconds) until idle clients should be closed with a GOAWAY frame. PING frames are not considered as activity.')); 376 | o.datatype = 'uinteger'; 377 | o.depends('transport', 'grpc'); 378 | o.depends({'transport': 'http', 'tls': '1'}); 379 | o.modalonly = true; 380 | 381 | if (features.with_grpc) { 382 | o = s.option(form.Value, 'http_ping_timeout', _('Ping timeout'), 383 | _('The timeout (in seconds) that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.')); 384 | o.datatype = 'uinteger'; 385 | o.depends('transport', 'grpc'); 386 | o.modalonly = true; 387 | } 388 | /* HTTP config end */ 389 | 390 | /* WebSocket config start */ 391 | o = s.option(form.Value, 'ws_host', _('Host')); 392 | o.depends('transport', 'ws'); 393 | o.modalonly = true; 394 | 395 | o = s.option(form.Value, 'ws_path', _('Path')); 396 | o.depends('transport', 'ws'); 397 | o.modalonly = true; 398 | 399 | o = s.option(form.Value, 'websocket_early_data', _('Early data'), 400 | _('Allowed payload size is in the request.')); 401 | o.datatype = 'uinteger'; 402 | o.value('2048'); 403 | o.depends('transport', 'ws'); 404 | o.modalonly = true; 405 | 406 | o = s.option(form.Value, 'websocket_early_data_header', _('Early data header name'), 407 | _('Early data is sent in path instead of header by default.') + 408 | '
' + 409 | _('To be compatible with Xray-core, set this to Sec-WebSocket-Protocol.')); 410 | o.value('Sec-WebSocket-Protocol'); 411 | o.depends('transport', 'ws'); 412 | o.modalonly = true; 413 | /* WebSocket config end */ 414 | 415 | /* Transport config end */ 416 | 417 | /* Mux config start */ 418 | o = s.option(form.Flag, 'multiplex', _('Multiplex')); 419 | o.default = o.disabled; 420 | o.depends('type', 'shadowsocks'); 421 | o.depends('type', 'trojan'); 422 | o.depends('type', 'vless'); 423 | o.depends('type', 'vmess'); 424 | o.modalonly = true; 425 | 426 | o = s.option(form.Flag, 'multiplex_padding', _('Enable padding')); 427 | o.default = o.disabled; 428 | o.depends('multiplex', '1'); 429 | o.modalonly = true; 430 | 431 | if (features.hp_has_tcp_brutal) { 432 | o = s.option(form.Flag, 'multiplex_brutal', _('Enable TCP Brutal'), 433 | _('Enable TCP Brutal congestion control algorithm')); 434 | o.default = o.disabled; 435 | o.depends('multiplex', '1'); 436 | o.modalonly = true; 437 | 438 | o = s.option(form.Value, 'multiplex_brutal_down', _('Download bandwidth'), 439 | _('Download bandwidth in Mbps.')); 440 | o.datatype = 'uinteger'; 441 | o.depends('multiplex_brutal', '1'); 442 | o.modalonly = true; 443 | 444 | o = s.option(form.Value, 'multiplex_brutal_up', _('Upload bandwidth'), 445 | _('Upload bandwidth in Mbps.')); 446 | o.datatype = 'uinteger'; 447 | o.depends('multiplex_brutal', '1'); 448 | o.modalonly = true; 449 | } 450 | /* Mux config end */ 451 | 452 | /* TLS config start */ 453 | o = s.option(form.Flag, 'tls', _('TLS')); 454 | o.default = o.disabled; 455 | o.depends('type', 'http'); 456 | o.depends('type', 'hysteria'); 457 | o.depends('type', 'hysteria2'); 458 | o.depends('type', 'naive'); 459 | o.depends('type', 'trojan'); 460 | o.depends('type', 'vless'); 461 | o.depends('type', 'vmess'); 462 | o.rmempty = false; 463 | o.validate = function(section_id, value) { 464 | if (section_id) { 465 | var type = this.map.lookupOption('type', section_id)[0].formvalue(section_id); 466 | var tls = this.map.findElement('id', 'cbid.homeproxy.%s.tls'.format(section_id)).firstElementChild; 467 | 468 | if (['hysteria', 'hysteria2', 'tuic'].includes(type)) { 469 | tls.checked = true; 470 | tls.disabled = true; 471 | } else { 472 | tls.disabled = null; 473 | } 474 | } 475 | 476 | return true; 477 | } 478 | o.modalonly = true; 479 | 480 | o = s.option(form.Value, 'tls_sni', _('TLS SNI'), 481 | _('Used to verify the hostname on the returned certificates unless insecure is given.')); 482 | o.depends({'tls': '1', 'tls_reality': '0'}); 483 | o.depends({'tls': '1', 'tls_reality': null}); 484 | o.modalonly = true; 485 | 486 | o = s.option(form.DynamicList, 'tls_alpn', _('TLS ALPN'), 487 | _('List of supported application level protocols, in order of preference.')); 488 | o.depends('tls', '1'); 489 | o.modalonly = true; 490 | 491 | o = s.option(form.ListValue, 'tls_min_version', _('Minimum TLS version'), 492 | _('The minimum TLS version that is acceptable.')); 493 | o.value('', _('default')); 494 | for (var i of hp.tls_versions) 495 | o.value(i); 496 | o.depends('tls', '1'); 497 | o.modalonly = true; 498 | 499 | o = s.option(form.ListValue, 'tls_max_version', _('Maximum TLS version'), 500 | _('The maximum TLS version that is acceptable.')); 501 | o.value('', _('default')); 502 | for (var i of hp.tls_versions) 503 | o.value(i); 504 | o.depends('tls', '1'); 505 | o.modalonly = true; 506 | 507 | o = s.option(form.MultiValue, 'tls_cipher_suites', _('Cipher suites'), 508 | _('The elliptic curves that will be used in an ECDHE handshake, in preference order. If empty, the default will be used.')); 509 | for (var i of hp.tls_cipher_suites) 510 | o.value(i); 511 | o.depends('tls', '1'); 512 | o.optional = true; 513 | o.modalonly = true; 514 | 515 | if (features.with_acme) { 516 | o = s.option(form.Flag, 'tls_acme', _('Enable ACME'), 517 | _('Use ACME TLS certificate issuer.')); 518 | o.default = o.disabled; 519 | o.depends('tls', '1'); 520 | o.modalonly = true; 521 | 522 | o = s.option(form.DynamicList, 'tls_acme_domain', _('Domains')); 523 | o.datatype = 'hostname'; 524 | o.depends('tls_acme', '1'); 525 | o.rmempty = false; 526 | o.modalonly = true; 527 | 528 | o = s.option(form.Value, 'tls_acme_dsn', _('Default server name'), 529 | _('Server name to use when choosing a certificate if the ClientHello\'s ServerName field is empty.')); 530 | o.depends('tls_acme', '1'); 531 | o.rmempty = false; 532 | o.modalonly = true; 533 | 534 | o = s.option(form.Value, 'tls_acme_email', _('Email'), 535 | _('The email address to use when creating or selecting an existing ACME server account.')); 536 | o.depends('tls_acme', '1'); 537 | o.validate = function(section_id, value) { 538 | if (section_id) { 539 | if (!value) 540 | return _('Expecting: %s').format('non-empty value'); 541 | else if (!value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) 542 | return _('Expecting: %s').format('valid email address'); 543 | } 544 | 545 | return true; 546 | } 547 | o.modalonly = true; 548 | 549 | o = s.option(form.Value, 'tls_acme_provider', _('CA provider'), 550 | _('The ACME CA provider to use.')); 551 | o.value('letsencrypt', _('Let\'s Encrypt')); 552 | o.value('zerossl', _('ZeroSSL')); 553 | o.depends('tls_acme', '1'); 554 | o.rmempty = false; 555 | o.modalonly = true; 556 | 557 | o = s.option(form.Flag, 'tls_dns01_challenge', _('DNS01 challenge')) 558 | o.default = o.disabled; 559 | o.depends('tls_acme', '1'); 560 | o.modalonly = true; 561 | 562 | o = s.option(form.ListValue, 'tls_dns01_provider', _('DNS provider')); 563 | o.value('alidns', _('Alibaba Cloud DNS')); 564 | o.value('cloudflare', _('Cloudflare')); 565 | o.depends('tls_dns01_challenge', '1'); 566 | o.default = 'cloudflare'; 567 | o.rmempty = false; 568 | o.modalonly = true; 569 | 570 | o = s.option(form.Value, 'tls_dns01_ali_akid', _('Access key ID')); 571 | o.depends('tls_dns01_provider', 'alidns'); 572 | o.rmempty = false; 573 | o.modalonly = true; 574 | 575 | o = s.option(form.Value, 'tls_dns01_ali_aksec', _('Access key secret')); 576 | o.depends('tls_dns01_provider', 'alidns'); 577 | o.rmempty = false; 578 | o.modalonly = true; 579 | 580 | o = s.option(form.Value, 'tls_dns01_ali_rid', _('Region ID')); 581 | o.depends('tls_dns01_provider', 'alidns'); 582 | o.rmempty = false; 583 | o.modalonly = true; 584 | 585 | o = s.option(form.Value, 'tls_dns01_cf_api_token', _('API token')); 586 | o.depends('tls_dns01_provider', 'cloudflare'); 587 | o.rmempty = false; 588 | o.modalonly = true; 589 | 590 | o = s.option(form.Flag, 'tls_acme_dhc', _('Disable HTTP challenge')); 591 | o.default = o.disabled; 592 | o.depends('tls_dns01_challenge', '0'); 593 | o.modalonly = true; 594 | 595 | o = s.option(form.Flag, 'tls_acme_dtac', _('Disable TLS ALPN challenge')); 596 | o.default = o.disabled; 597 | o.depends('tls_dns01_challenge', '0'); 598 | o.modalonly = true; 599 | 600 | o = s.option(form.Value, 'tls_acme_ahp', _('Alternative HTTP port'), 601 | _('The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a listener for the HTTP challenge.')); 602 | o.datatype = 'port'; 603 | o.depends('tls_dns01_challenge', '0'); 604 | o.modalonly = true; 605 | 606 | o = s.option(form.Value, 'tls_acme_atp', _('Alternative TLS port'), 607 | _('The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to succeed.')); 608 | o.datatype = 'port'; 609 | o.depends('tls_dns01_challenge', '0'); 610 | o.modalonly = true; 611 | 612 | o = s.option(form.Flag, 'tls_acme_external_account', _('External Account Binding'), 613 | _('EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known by the CA.' + 614 | '
External account bindings are "used to associate an ACME account with an existing account in a non-ACME system, such as a CA customer database.')); 615 | o.default = o.disabled; 616 | o.depends('tls_acme', '1'); 617 | o.modalonly = true; 618 | 619 | o = s.option(form.Value, 'tls_acme_ea_keyid', _('External account key ID')); 620 | o.depends('tls_acme_external_account', '1'); 621 | o.rmempty = false; 622 | o.modalonly = true; 623 | 624 | o = s.option(form.Value, 'tls_acme_ea_mackey', _('External account MAC key')); 625 | o.depends('tls_acme_external_account', '1'); 626 | o.rmempty = false; 627 | o.modalonly = true; 628 | } 629 | 630 | if (features.with_reality_server) { 631 | o = s.option(form.Flag, 'tls_reality', _('REALITY')); 632 | o.default = o.disabled; 633 | o.depends({'tls': '1', 'tls_acme': '0', 'type': 'vless'}); 634 | o.depends({'tls': '1', 'tls_acme': null, 'type': 'vless'}); 635 | o.modalonly = true; 636 | 637 | o = s.option(form.Value, 'tls_reality_private_key', _('REALITY private key')); 638 | o.depends('tls_reality', '1'); 639 | o.rmempty = false; 640 | o.modalonly = true; 641 | 642 | o = s.option(form.DynamicList, 'tls_reality_short_id', _('REALITY short ID')); 643 | o.depends('tls_reality', '1'); 644 | o.rmempty = false; 645 | o.modalonly = true; 646 | 647 | o = s.option(form.Value, 'tls_reality_max_time_difference', _('Max time difference'), 648 | _('The maximum time difference between the server and the client.')); 649 | o.depends('tls_reality', '1'); 650 | o.modalonly = true; 651 | 652 | o = s.option(form.Value, 'tls_reality_server_addr', _('Handshake server address')); 653 | o.datatype = 'hostname'; 654 | o.depends('tls_reality', '1'); 655 | o.rmempty = false; 656 | o.modalonly = true; 657 | 658 | o = s.option(form.Value, 'tls_reality_server_port', _('Handshake server port')); 659 | o.datatype = 'port'; 660 | o.depends('tls_reality', '1'); 661 | o.rmempty = false; 662 | o.modalonly = true; 663 | } 664 | 665 | o = s.option(form.Value, 'tls_cert_path', _('Certificate path'), 666 | _('The server public key, in PEM format.')); 667 | o.value('/etc/homeproxy/certs/server_publickey.pem'); 668 | o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': null}); 669 | o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': '0'}); 670 | o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': '0'}); 671 | o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': null}); 672 | o.rmempty = false; 673 | o.modalonly = true; 674 | 675 | o = s.option(form.Button, '_upload_cert', _('Upload certificate'), 676 | _('Save your configuration before uploading files!')); 677 | o.inputstyle = 'action'; 678 | o.inputtitle = _('Upload...'); 679 | o.depends({'tls': '1', 'tls_cert_path': '/etc/homeproxy/certs/server_publickey.pem'}); 680 | o.onclick = L.bind(hp.uploadCertificate, this, _('certificate'), 'server_publickey'); 681 | o.modalonly = true; 682 | 683 | o = s.option(form.Value, 'tls_key_path', _('Key path'), 684 | _('The server private key, in PEM format.')); 685 | o.value('/etc/homeproxy/certs/server_privatekey.pem'); 686 | o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': '0'}); 687 | o.depends({'tls': '1', 'tls_acme': '0', 'tls_reality': null}); 688 | o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': '0'}); 689 | o.depends({'tls': '1', 'tls_acme': null, 'tls_reality': null}); 690 | o.rmempty = false; 691 | o.modalonly = true; 692 | 693 | o = s.option(form.Button, '_upload_key', _('Upload key'), 694 | _('Save your configuration before uploading files!')); 695 | o.inputstyle = 'action'; 696 | o.inputtitle = _('Upload...'); 697 | o.depends({'tls': '1', 'tls_key_path': '/etc/homeproxy/certs/server_privatekey.pem'}); 698 | o.onclick = L.bind(hp.uploadCertificate, this, _('private key'), 'server_privatekey'); 699 | o.modalonly = true; 700 | /* TLS config end */ 701 | 702 | /* Extra settings start */ 703 | o = s.option(form.Flag, 'tcp_fast_open', _('TCP fast open'), 704 | _('Enable tcp fast open for listener.')); 705 | o.default = o.disabled; 706 | o.depends({'network': 'udp', '!reverse': true}); 707 | o.modalonly = true; 708 | 709 | o = s.option(form.Flag, 'tcp_multi_path', _('MultiPath TCP')); 710 | o.default = o.disabled; 711 | o.depends({'network': 'udp', '!reverse': true}); 712 | o.modalonly = true; 713 | 714 | o = s.option(form.Flag, 'udp_fragment', _('UDP Fragment'), 715 | _('Enable UDP fragmentation.')); 716 | o.default = o.disabled; 717 | o.depends({'network': 'tcp', '!reverse': true}); 718 | o.modalonly = true; 719 | 720 | o = s.option(form.Flag, 'sniff_override', _('Override destination'), 721 | _('Override the connection destination address with the sniffed domain.')); 722 | o.rmempty = false; 723 | 724 | o = s.option(form.ListValue, 'domain_strategy', _('Domain strategy'), 725 | _('If set, the requested domain name will be resolved to IP before routing.')); 726 | for (var i in hp.dns_strategy) 727 | o.value(i, hp.dns_strategy[i]) 728 | o.modalonly = true; 729 | 730 | o = s.option(form.ListValue, 'network', _('Network')); 731 | o.value('tcp', _('TCP')); 732 | o.value('udp', _('UDP')); 733 | o.value('', _('Both')); 734 | o.depends('type', 'naive'); 735 | o.depends('type', 'shadowsocks'); 736 | o.modalonly = true; 737 | /* Extra settings end */ 738 | 739 | return m.render(); 740 | } 741 | }); 742 | -------------------------------------------------------------------------------- /htdocs/luci-static/resources/view/homeproxy/status.js: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only 2 | * 3 | * Copyright (C) 2022-2023 ImmortalWrt.org 4 | */ 5 | 6 | 'use strict'; 7 | 'require dom'; 8 | 'require form'; 9 | 'require fs'; 10 | 'require poll'; 11 | 'require rpc'; 12 | 'require uci'; 13 | 'require ui'; 14 | 'require view'; 15 | 16 | /* Thanks to luci-app-aria2 */ 17 | var css = ' \ 18 | #log_textarea { \ 19 | padding: 10px; \ 20 | text-align: left; \ 21 | } \ 22 | #log_textarea pre { \ 23 | padding: .5rem; \ 24 | word-break: break-all; \ 25 | margin: 0; \ 26 | } \ 27 | .description { \ 28 | background-color: #33ccff; \ 29 | }'; 30 | 31 | var hp_dir = '/var/run/homeproxy'; 32 | 33 | function getConnStat(self, site) { 34 | var callConnStat = rpc.declare({ 35 | object: 'luci.homeproxy', 36 | method: 'connection_check', 37 | params: ['site'], 38 | expect: { '': {} } 39 | }); 40 | 41 | self.default = E('div', { 'style': 'cbi-value-field' }, [ 42 | E('button', { 43 | 'class': 'btn cbi-button cbi-button-action', 44 | 'click': ui.createHandlerFn(this, function() { 45 | return L.resolveDefault(callConnStat(site), {}).then((ret) => { 46 | var ele = self.default.firstElementChild.nextElementSibling; 47 | if (ret.result) { 48 | ele.style.setProperty('color', 'green'); 49 | ele.innerHTML = _('passed'); 50 | } else { 51 | ele.style.setProperty('color', 'red'); 52 | ele.innerHTML = _('failed'); 53 | } 54 | }); 55 | }) 56 | }, [ _('Check') ]), 57 | ' ', 58 | E('strong', { 'style': 'color:gray' }, _('unchecked')), 59 | ]); 60 | } 61 | 62 | function getResVersion(self, type) { 63 | var callResVersion = rpc.declare({ 64 | object: 'luci.homeproxy', 65 | method: 'resources_get_version', 66 | params: ['type'], 67 | expect: { '': {} } 68 | }); 69 | 70 | var callResUpdate = rpc.declare({ 71 | object: 'luci.homeproxy', 72 | method: 'resources_update', 73 | params: ['type'], 74 | expect: { '': {} } 75 | }); 76 | 77 | return L.resolveDefault(callResVersion(type), {}).then((res) => { 78 | var spanTemp = E('div', { 'style': 'cbi-value-field' }, [ 79 | E('button', { 80 | 'class': 'btn cbi-button cbi-button-action', 81 | 'click': ui.createHandlerFn(this, function() { 82 | return L.resolveDefault(callResUpdate(type), {}).then((res) => { 83 | switch (res.status) { 84 | case 0: 85 | self.description = _('Successfully updated.'); 86 | break; 87 | case 1: 88 | self.description = _('Update failed.'); 89 | break; 90 | case 2: 91 | self.description = _('Already in updating.'); 92 | break; 93 | case 3: 94 | self.description = _('Already at the latest version.'); 95 | break; 96 | default: 97 | self.description = _('Unknown error.'); 98 | break; 99 | } 100 | 101 | return self.map.reset(); 102 | }); 103 | }) 104 | }, [ _('Check update') ]), 105 | ' ', 106 | E('strong', { 'style': (res.error ? 'color:red' : 'color:green') }, 107 | [ res.error ? 'not found' : res.version ] 108 | ), 109 | ]); 110 | 111 | self.default = spanTemp; 112 | }); 113 | } 114 | 115 | function getRuntimeLog(name, filename) { 116 | var callLogClean = rpc.declare({ 117 | object: 'luci.homeproxy', 118 | method: 'log_clean', 119 | params: ['type'], 120 | expect: { '': {} } 121 | }); 122 | 123 | var log_textarea = E('div', { 'id': 'log_textarea' }, 124 | E('img', { 125 | 'src': L.resource(['icons/loading.gif']), 126 | 'alt': _('Loading'), 127 | 'style': 'vertical-align:middle' 128 | }, _('Collecting data...')) 129 | ); 130 | 131 | var log; 132 | poll.add(L.bind(function() { 133 | return fs.read_direct(String.format('%s/%s.log', hp_dir, filename), 'text') 134 | .then(function(res) { 135 | log = E('pre', { 'wrap': 'pre' }, [ 136 | res.trim() || _('Log is empty.') 137 | ]); 138 | 139 | dom.content(log_textarea, log); 140 | }).catch(function(err) { 141 | if (err.toString().includes('NotFoundError')) 142 | log = E('pre', { 'wrap': 'pre' }, [ 143 | _('Log file does not exist.') 144 | ]); 145 | else 146 | log = E('pre', { 'wrap': 'pre' }, [ 147 | _('Unknown error: %s').format(err) 148 | ]); 149 | 150 | dom.content(log_textarea, log); 151 | }); 152 | })); 153 | 154 | return E([ 155 | E('style', [ css ]), 156 | E('div', {'class': 'cbi-map'}, [ 157 | E('h3', {'name': 'content'}, [ 158 | _('%s log').format(name), 159 | ' ', 160 | E('button', { 161 | 'class': 'btn cbi-button cbi-button-action', 162 | 'click': ui.createHandlerFn(this, function() { 163 | return L.resolveDefault(callLogClean(filename), {}); 164 | }) 165 | }, [ _('Clean log') ]) 166 | ]), 167 | E('div', {'class': 'cbi-section'}, [ 168 | log_textarea, 169 | E('div', {'style': 'text-align:right'}, 170 | E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval)) 171 | ) 172 | ]) 173 | ]) 174 | ]); 175 | } 176 | 177 | return view.extend({ 178 | load: function() { 179 | return Promise.all([ 180 | uci.load('homeproxy') 181 | ]); 182 | }, 183 | 184 | render: function(data) { 185 | var m, s, o; 186 | var routing_mode = uci.get(data[0], 'config', 'routing_mode') || 'bypass_mainland_china'; 187 | 188 | m = new form.Map('homeproxy'); 189 | 190 | s = m.section(form.NamedSection, 'config', 'homeproxy', _('Connection check')); 191 | s.anonymous = true; 192 | 193 | o = s.option(form.DummyValue, '_check_baidu', _('BaiDu')); 194 | o.cfgvalue = function() { return getConnStat(this, 'baidu') }; 195 | 196 | o = s.option(form.DummyValue, '_check_google', _('Google')); 197 | o.cfgvalue = function() { return getConnStat(this, 'google') }; 198 | 199 | 200 | s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management')); 201 | s.anonymous = true; 202 | 203 | o = s.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version')); 204 | o.cfgvalue = function() { return getResVersion(this, 'china_ip4') }; 205 | o.rawhtml = true; 206 | 207 | o = s.option(form.DummyValue, '_china_ip6_version', _('China IPv6 list version')); 208 | o.cfgvalue = function() { return getResVersion(this, 'china_ip6') }; 209 | o.rawhtml = true; 210 | 211 | o = s.option(form.DummyValue, '_china_list_version', _('China list version')); 212 | o.cfgvalue = function() { return getResVersion(this, 'china_list') }; 213 | o.rawhtml = true; 214 | 215 | o = s.option(form.DummyValue, '_gfw_list_version', _('GFW list version')); 216 | o.cfgvalue = function() { return getResVersion(this, 'gfw_list') }; 217 | o.rawhtml = true; 218 | 219 | s = m.section(form.NamedSection, 'config', 'homeproxy'); 220 | s.anonymous = true; 221 | 222 | o = s.option(form.DummyValue, '_homeproxy_logview'); 223 | o.render = L.bind(getRuntimeLog, this, _('HomeProxy'), 'homeproxy'); 224 | 225 | o = s.option(form.DummyValue, '_sing-box-c_logview'); 226 | o.render = L.bind(getRuntimeLog, this, _('sing-box client'), 'sing-box-c'); 227 | 228 | o = s.option(form.DummyValue, '_sing-box-s_logview'); 229 | o.render = L.bind(getRuntimeLog, this, _('sing-box server'), 'sing-box-s'); 230 | 231 | return m.render(); 232 | }, 233 | 234 | handleSaveApply: null, 235 | handleSave: null, 236 | handleReset: null 237 | }); 238 | -------------------------------------------------------------------------------- /root/etc/capabilities/homeproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "bounding": [ 3 | "CAP_NET_ADMIN", 4 | "CAP_NET_BIND_SERVICE", 5 | "CAP_NET_RAW", 6 | "CAP_SYS_PTRACE" 7 | ], 8 | "effective": [ 9 | "CAP_NET_ADMIN", 10 | "CAP_NET_BIND_SERVICE", 11 | "CAP_NET_RAW", 12 | "CAP_SYS_PTRACE" 13 | ], 14 | "ambient": [ 15 | "CAP_NET_ADMIN", 16 | "CAP_NET_BIND_SERVICE", 17 | "CAP_NET_RAW", 18 | "CAP_SYS_PTRACE" 19 | ], 20 | "permitted": [ 21 | "CAP_NET_ADMIN", 22 | "CAP_NET_BIND_SERVICE", 23 | "CAP_NET_RAW", 24 | "CAP_SYS_PTRACE" 25 | ], 26 | "inheritable": [ 27 | "CAP_NET_ADMIN", 28 | "CAP_NET_BIND_SERVICE", 29 | "CAP_NET_RAW", 30 | "CAP_SYS_PTRACE" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /root/etc/config/homeproxy: -------------------------------------------------------------------------------- 1 | 2 | config homeproxy 'infra' 3 | option __warning 'DO NOT EDIT THIS SECTION, OR YOU ARE ON YOUR OWN!' 4 | option common_port '22,53,80,143,443,465,853,873,993,995,8080,8443,9418' 5 | option mixed_port '5330' 6 | option redirect_port '5331' 7 | option tproxy_port '5332' 8 | option dns_port '5333' 9 | option china_dns_port '5334' 10 | option tun_name 'singtun0' 11 | option tun_addr4 '172.19.0.1/30' 12 | option tun_addr6 'fdfe:dcba:9876::1/126' 13 | option tun_mtu '9000' 14 | option table_mark '100' 15 | option self_mark '100' 16 | option tproxy_mark '101' 17 | option tun_mark '102' 18 | 19 | config homeproxy 'config' 20 | option main_node 'nil' 21 | option main_udp_node 'same' 22 | option dns_server '8.8.8.8' 23 | option routing_mode 'bypass_mainland_china' 24 | option routing_port 'common' 25 | option proxy_mode 'redirect_tproxy' 26 | option ipv6_support '1' 27 | 28 | config homeproxy 'control' 29 | option lan_proxy_mode 'disabled' 30 | list wan_proxy_ipv4_ips '91.108.4.0/22' 31 | list wan_proxy_ipv4_ips '91.108.8.0/22' 32 | list wan_proxy_ipv4_ips '91.108.12.0/22' 33 | list wan_proxy_ipv4_ips '91.108.56.0/22' 34 | list wan_proxy_ipv4_ips '95.161.64.0/20' 35 | list wan_proxy_ipv4_ips '149.154.160.0/22' 36 | list wan_proxy_ipv4_ips '149.154.164.0/22' 37 | list wan_proxy_ipv4_ips '149.154.172.0/22' 38 | 39 | config homeproxy 'routing' 40 | option sniff_override '1' 41 | option default_outbound 'direct-out' 42 | 43 | config homeproxy 'dns' 44 | option dns_strategy 'prefer_ipv4' 45 | option default_server 'local-dns' 46 | option disable_cache '0' 47 | option disable_cache_expire '0' 48 | 49 | config homeproxy 'subscription' 50 | option auto_update '0' 51 | option allow_insecure '0' 52 | option packet_encoding 'xudp' 53 | option update_via_proxy '0' 54 | option filter_nodes 'disabled' 55 | 56 | config homeproxy 'server' 57 | option enabled '0' 58 | option auto_firewall '0' 59 | 60 | config homeproxy 'experimental' 61 | option enable_clash_api '0' -------------------------------------------------------------------------------- /root/etc/homeproxy/resources/china_ip4.ver: -------------------------------------------------------------------------------- 1 | 20240602150004 2 | -------------------------------------------------------------------------------- /root/etc/homeproxy/resources/china_ip6.txt: -------------------------------------------------------------------------------- 1 | 2001:250::/30 2 | 2001:254::/31 3 | 2001:256:100::/48 4 | 2001:550:1601::/48 5 | 2001:67c:c28::/48 6 | 2001:7fa:5::/48 7 | 2001:c68::/32 8 | 2001:cc0::/32 9 | 2001:da8::/32 10 | 2001:daa:1::/48 11 | 2001:daa:2::/47 12 | 2001:daa:4::/47 13 | 2001:daa:6::/48 14 | 2001:dc7::/32 15 | 2001:dd8:1::/48 16 | 2001:dd8:5::/48 17 | 2001:dd9::/48 18 | 2001:df0:2e00::/48 19 | 2001:df0:ac40::/48 20 | 2001:df1:2b40::/48 21 | 2001:df1:5fc0::/48 22 | 2001:df1:6b80::/48 23 | 2001:df1:bd80::/48 24 | 2001:df3:15c0::/48 25 | 2001:df3:3a80::/48 26 | 2001:df3:8b80::/48 27 | 2001:df3:b380::/48 28 | 2001:df3:d0c0::/48 29 | 2001:df5:7800::/48 30 | 2001:df7:1480::/48 31 | 2400:1160::/32 32 | 2400:3200::/32 33 | 2400:5280:f803::/48 34 | 2400:5400:10::/48 35 | 2400:5a60:2::/48 36 | 2400:5a60:100::/48 37 | 2400:6000::/32 38 | 2400:6460::/40 39 | 2400:6600::/32 40 | 2400:6e60:1301::/48 41 | 2400:73e0::/32 42 | 2400:75aa::/32 43 | 2400:7bc0:20::/43 44 | 2400:7fc0::/40 45 | 2400:7fc0:220::/44 46 | 2400:7fc0:240::/44 47 | 2400:7fc0:2a0::/44 48 | 2400:7fc0:2c0::/44 49 | 2400:7fc0:4000::/40 50 | 2400:7fc0:8000::/36 51 | 2400:7fc0:c000::/36 52 | 2400:8200::/32 53 | 2400:87c0::/32 54 | 2400:89c0:1010::/44 55 | 2400:89c0:1020::/43 56 | 2400:89c0:1050::/46 57 | 2400:89c0:1130::/44 58 | 2400:89c0:1150::/48 59 | 2400:89c0:2100::/48 60 | 2400:89c0:2200::/48 61 | 2400:89c0:3010::/44 62 | 2400:89c0:6000::/48 63 | 2400:89c0:6100::/48 64 | 2400:9020:f010::/46 65 | 2400:9340::/32 66 | 2400:9380:8001::/48 67 | 2400:9380:8003::/48 68 | 2400:9380:8021::/48 69 | 2400:9380:8040::/48 70 | 2400:9380:8140::/48 71 | 2400:9380:8201::/48 72 | 2400:9380:8301::/48 73 | 2400:9380:9001::/48 74 | 2400:9380:9002::/48 75 | 2400:9380:9005::/48 76 | 2400:9380:9009::/48 77 | 2400:9380:900a::/48 78 | 2400:9380:9020::/47 79 | 2400:9380:9040::/47 80 | 2400:9380:9050::/47 81 | 2400:9380:9060::/48 82 | 2400:9380:9071::/48 83 | 2400:9380:9080::/47 84 | 2400:9380:90a0::/47 85 | 2400:9380:90b0::/45 86 | 2400:9380:9100::/47 87 | 2400:9380:9121::/48 88 | 2400:9380:9201::/48 89 | 2400:9380:9202::/48 90 | 2400:9380:9220::/47 91 | 2400:9380:9240::/47 92 | 2400:9380:9250::/47 93 | 2400:9380:9252::/48 94 | 2400:9380:9260::/48 95 | 2400:9380:9271::/48 96 | 2400:9380:9272::/48 97 | 2400:9380:9280::/47 98 | 2400:9380:9282::/48 99 | 2400:9380:92a0::/47 100 | 2400:9380:92b0::/45 101 | 2400:9520:434::/48 102 | 2400:95e0::/48 103 | 2400:9600:8800::/48 104 | 2400:a040::/32 105 | 2400:a980::/29 106 | 2400:ae00:1981::/48 107 | 2400:b200::/32 108 | 2400:b600::/32 109 | 2400:be00::/48 110 | 2400:cb80:e30::/44 111 | 2400:cb80:e40::/44 112 | 2400:da00::/32 113 | 2400:dd00::/28 114 | 2400:ebc0::/32 115 | 2400:ee00::/32 116 | 2400:f6e0::/32 117 | 2400:f720::/32 118 | 2400:f7c0::/32 119 | 2400:fb40::/32 120 | 2400:fe00::/32 121 | 2401:20::/40 122 | 2401:800::/32 123 | 2401:e60:10::/44 124 | 2401:1160::/32 125 | 2401:11a0:10::/44 126 | 2401:11a0:150::/44 127 | 2401:11a0:1500::/40 128 | 2401:11a0:d150::/48 129 | 2401:11a0:d152::/48 130 | 2401:11a0:d158::/48 131 | 2401:1200::/48 132 | 2401:1320::/32 133 | 2401:1740:2000::/48 134 | 2401:1d40::/32 135 | 2401:2780::/32 136 | 2401:2e00::/32 137 | 2401:33c0::/32 138 | 2401:3480::/36 139 | 2401:3480:2000::/48 140 | 2401:3480:3000::/36 141 | 2401:34a0::/31 142 | 2401:3800::/32 143 | 2401:3880::/32 144 | 2401:3980::/32 145 | 2401:3a80::/32 146 | 2401:3b80::/32 147 | 2401:3c80::/32 148 | 2401:3d80::/32 149 | 2401:3e80::/32 150 | 2401:3f80::/32 151 | 2401:4180::/32 152 | 2401:4280::/32 153 | 2401:4380::/32 154 | 2401:4480::/32 155 | 2401:4580::/32 156 | 2401:4680::/32 157 | 2401:4780::/32 158 | 2401:4880::/32 159 | 2401:4a80::/32 160 | 2401:5960:5960::/48 161 | 2401:70e0::/32 162 | 2401:7700::/32 163 | 2401:7d40::/32 164 | 2401:7e00::/32 165 | 2401:8d00::/46 166 | 2401:8d00:7::/48 167 | 2401:8d00:8::/47 168 | 2401:8d00:b::/48 169 | 2401:8d00:c::/48 170 | 2401:8d00:f::/48 171 | 2401:8d00:10::/48 172 | 2401:8d00:12::/48 173 | 2401:8d00:14::/48 174 | 2401:a140:1::/48 175 | 2401:b180::/32 176 | 2401:b400::/45 177 | 2401:b400:8::/47 178 | 2401:b400:11::/48 179 | 2401:b400:14::/46 180 | 2401:b400:20::/47 181 | 2401:b680::/32 182 | 2401:be00::/32 183 | 2401:cb80::/32 184 | 2401:cc00::/32 185 | 2401:ce00::/32 186 | 2401:d180::/46 187 | 2401:d180:10::/47 188 | 2401:d180:2120::/48 189 | 2401:de00::/32 190 | 2401:ec00::/32 191 | 2401:fa00:40::/43 192 | 2402:840:d000::/46 193 | 2402:840:e000::/46 194 | 2402:840:f000::/38 195 | 2402:1440::/32 196 | 2402:2000::/32 197 | 2402:2b40:8000::/36 198 | 2402:3180:8000::/33 199 | 2402:3c00::/32 200 | 2402:3f80:1400::/40 201 | 2402:4440::/32 202 | 2402:4b80::/32 203 | 2402:4e00::/32 204 | 2402:5e40::/32 205 | 2402:5ec0::/32 206 | 2402:6e80::/32 207 | 2402:6f40::/48 208 | 2402:6f40:2::/47 209 | 2402:6fc0::/48 210 | 2402:7040::/32 211 | 2402:7240::/36 212 | 2402:7d80::/32 213 | 2402:8bc0::/32 214 | 2402:8cc0::/40 215 | 2402:8cc0:200::/40 216 | 2402:92c0::/48 217 | 2402:93c0::/48 218 | 2402:93c0:20::/48 219 | 2402:93c0:100::/48 220 | 2402:9a80::/32 221 | 2402:9e80:60::/48 222 | 2402:a200::/32 223 | 2402:b8c0::/32 224 | 2402:b940::/40 225 | 2402:b940:200::/39 226 | 2402:d340::/32 227 | 2402:db40::/33 228 | 2402:dfc0::/44 229 | 2402:dfc0:50::/44 230 | 2402:e380:100::/40 231 | 2402:e380:302::/48 232 | 2402:e380:306::/48 233 | 2402:e380:308::/48 234 | 2402:e380:30c::/48 235 | 2402:e380:312::/48 236 | 2402:e480::/32 237 | 2402:e740::/32 238 | 2402:e7c0::/32 239 | 2402:ef40::/32 240 | 2402:f000::/32 241 | 2402:f8c0::/32 242 | 2403:600::/32 243 | 2403:c80::/32 244 | 2403:1a40::/32 245 | 2403:1b80::/48 246 | 2403:1ec0:1200::/48 247 | 2403:1ec0:1400::/48 248 | 2403:1ec0:1610::/48 249 | 2403:2040::/32 250 | 2403:27c0:c03::/48 251 | 2403:27c0:1000::/48 252 | 2403:2b40::/32 253 | 2403:3140::/32 254 | 2403:4240::/32 255 | 2403:4280::/47 256 | 2403:4b40::/32 257 | 2403:4c80::/48 258 | 2403:5c80::/48 259 | 2403:6380:14::/47 260 | 2403:6380:20::/44 261 | 2403:6380:40::/48 262 | 2403:6380:42::/47 263 | 2403:6a00::/32 264 | 2403:7580::/32 265 | 2403:8080:101::/48 266 | 2403:8c00::/32 267 | 2403:9b00::/32 268 | 2403:a100::/48 269 | 2403:a140:10::/48 270 | 2403:a140:100::/40 271 | 2403:a200::/32 272 | 2403:ac00::/32 273 | 2403:ad80:8008::/48 274 | 2403:b400::/32 275 | 2403:c980::/32 276 | 2403:d400::/32 277 | 2403:e7c0:1::/48 278 | 2403:f4c0::/48 279 | 2403:ffc0:1100::/40 280 | 2403:ffc0:1200::/39 281 | 2404:bc0:1::/48 282 | 2404:bc0:4000::/43 283 | 2404:bc0:4100::/43 284 | 2404:bc0:4200::/43 285 | 2404:bc0:4300::/44 286 | 2404:bc0:4400::/43 287 | 2404:bc0:4f00::/43 288 | 2404:1c80::/32 289 | 2404:2280:106::/47 290 | 2404:2280:10d::/48 291 | 2404:2280:10f::/48 292 | 2404:2280:112::/47 293 | 2404:2280:115::/48 294 | 2404:2280:11b::/48 295 | 2404:2280:11c::/47 296 | 2404:2280:11e::/48 297 | 2404:2280:123::/48 298 | 2404:2280:126::/47 299 | 2404:2280:12e::/48 300 | 2404:2280:134::/48 301 | 2404:2280:136::/47 302 | 2404:2280:13b::/48 303 | 2404:2280:13c::/47 304 | 2404:2280:142::/48 305 | 2404:2280:147::/48 306 | 2404:2280:152::/48 307 | 2404:2280:193::/48 308 | 2404:2280:196::/48 309 | 2404:2280:199::/48 310 | 2404:2280:19a::/47 311 | 2404:2280:19c::/47 312 | 2404:2280:19e::/48 313 | 2404:2280:1a4::/47 314 | 2404:2280:1a8::/48 315 | 2404:2280:1b0::/48 316 | 2404:2280:1b2::/48 317 | 2404:2280:1b4::/46 318 | 2404:2280:1b8::/47 319 | 2404:2280:1ba::/48 320 | 2404:2280:1bf::/48 321 | 2404:2280:1c1::/48 322 | 2404:2280:1c2::/47 323 | 2404:2280:1c4::/47 324 | 2404:2280:1c7::/48 325 | 2404:2280:1c8::/48 326 | 2404:2280:1cb::/48 327 | 2404:2280:1cc::/48 328 | 2404:2280:1cf::/48 329 | 2404:2280:1d0::/48 330 | 2404:2280:1d3::/48 331 | 2404:2280:1d6::/48 332 | 2404:2280:1d8::/45 333 | 2404:2280:1e0::/48 334 | 2404:2280:1e2::/47 335 | 2404:2280:1e4::/46 336 | 2404:2280:1e8::/48 337 | 2404:2280:1ea::/48 338 | 2404:2280:1ec::/47 339 | 2404:2280:1ee::/48 340 | 2404:2280:1f0::/45 341 | 2404:2280:1f8::/46 342 | 2404:4dc0::/32 343 | 2404:6380::/48 344 | 2404:6380:1000::/48 345 | 2404:6380:8001::/48 346 | 2404:6500:dcb3::/48 347 | 2404:7180:a000::/48 348 | 2404:7180:a010::/48 349 | 2404:7180:a021::/48 350 | 2404:7180:aa00::/48 351 | 2404:7180:aa10::/48 352 | 2404:7180:b001::/48 353 | 2404:7180:b002::/48 354 | 2404:7180:b010::/48 355 | 2404:7180:c001::/48 356 | 2404:7180:c002::/48 357 | 2404:7180:c011::/48 358 | 2404:7180:c012::/48 359 | 2404:7180:f000::/48 360 | 2404:7180:f010::/48 361 | 2404:7240::/33 362 | 2404:7600::/32 363 | 2404:7940::/32 364 | 2404:8d02:28c8::/48 365 | 2404:8d02:4881::/48 366 | 2404:c2c0::/40 367 | 2404:c2c0:240::/44 368 | 2404:c2c0:280::/44 369 | 2404:c2c0:2c0::/44 370 | 2404:c2c0:501::/48 371 | 2404:c2c0:4000::/40 372 | 2404:c2c0:8000::/36 373 | 2404:c2c0:c000::/36 374 | 2404:c300::/32 375 | 2404:c940::/48 376 | 2404:d7c0:2::/48 377 | 2404:e280::/47 378 | 2404:e5c0::/32 379 | 2404:e8c0::/32 380 | 2404:ea80:500:575::/64 381 | 2404:ea80:600:623::/64 382 | 2404:ea80:700:104::/64 383 | 2404:ea80:700:112::/63 384 | 2404:ea80:700:695::/64 385 | 2404:ea80:700:696::/63 386 | 2404:ea80:700:696a::/64 387 | 2404:f4c0::/32 388 | 2405:6c0:1::/48 389 | 2405:6c0:2::/48 390 | 2405:6c0:4::/48 391 | 2405:1480:1000::/48 392 | 2405:1480:2000::/48 393 | 2405:1480:3000::/47 394 | 2405:1640:6::/48 395 | 2405:3140:11::/48 396 | 2405:3140:31::/48 397 | 2405:3140:3a::/48 398 | 2405:3bc0::/48 399 | 2405:57c0::/47 400 | 2405:57c0:100::/48 401 | 2405:66c0::/32 402 | 2405:68c0:21::/48 403 | 2405:6940::/48 404 | 2405:6f00:c101::/48 405 | 2405:6f00:c102::/48 406 | 2405:6f00:c170::/47 407 | 2405:6f00:c602::/48 408 | 2405:7040:6000::/47 409 | 2405:78c0:6e00::/43 410 | 2405:8280::/32 411 | 2405:8a40::/32 412 | 2405:a900:ffee::/48 413 | 2405:a900:fffe::/48 414 | 2405:ad00::/32 415 | 2405:b7c0::/32 416 | 2405:be80::/32 417 | 2405:d900::/32 418 | 2405:e000::/32 419 | 2405:f580::/32 420 | 2405:f940::/32 421 | 2406:280::/32 422 | 2406:840:1::/48 423 | 2406:840:2::/48 424 | 2406:840:7::/48 425 | 2406:840:10::/45 426 | 2406:840:20::/48 427 | 2406:840:44::/47 428 | 2406:840:78::/47 429 | 2406:840:80::/47 430 | 2406:840:90::/48 431 | 2406:840:100::/48 432 | 2406:840:110::/48 433 | 2406:840:180::/48 434 | 2406:840:1c0::/48 435 | 2406:840:301::/48 436 | 2406:840:302::/48 437 | 2406:840:3c1::/48 438 | 2406:840:410::/48 439 | 2406:840:420::/48 440 | 2406:840:680::/48 441 | 2406:840:801::/48 442 | 2406:840:802::/47 443 | 2406:840:840::/47 444 | 2406:840:860::/48 445 | 2406:840:880::/48 446 | 2406:840:9b1::/48 447 | 2406:840:a08::/48 448 | 2406:840:a90::/48 449 | 2406:840:c00::/47 450 | 2406:840:c60::/48 451 | 2406:840:1800::/48 452 | 2406:840:2800::/48 453 | 2406:840:3800::/48 454 | 2406:840:4100::/47 455 | 2406:840:43c0::/47 456 | 2406:840:4800::/47 457 | 2406:840:4880::/47 458 | 2406:840:5100::/47 459 | 2406:840:5800::/47 460 | 2406:840:5860::/47 461 | 2406:840:5880::/47 462 | 2406:840:8100::/40 463 | 2406:840:9000::/44 464 | 2406:840:9100::/47 465 | 2406:840:9102::/48 466 | 2406:840:9200::/40 467 | 2406:840:9680::/44 468 | 2406:840:9700::/40 469 | 2406:840:9801::/48 470 | 2406:840:9810::/48 471 | 2406:840:981c::/46 472 | 2406:840:9960::/47 473 | 2406:840:9962::/48 474 | 2406:840:9969::/48 475 | 2406:840:996f::/48 476 | 2406:840:9980::/48 477 | 2406:840:9984::/48 478 | 2406:840:c0a8::/48 479 | 2406:840:e030::/47 480 | 2406:840:e033::/48 481 | 2406:840:e03f::/48 482 | 2406:840:e080::/44 483 | 2406:840:e0c1::/48 484 | 2406:840:e0cf::/48 485 | 2406:840:e0e0::/44 486 | 2406:840:e10f::/48 487 | 2406:840:e14f::/48 488 | 2406:840:e180::/44 489 | 2406:840:e20f::/48 490 | 2406:840:e230::/48 491 | 2406:840:e270::/44 492 | 2406:840:e300::/47 493 | 2406:840:e302::/48 494 | 2406:840:e340::/44 495 | 2406:840:e351::/48 496 | 2406:840:e354::/48 497 | 2406:840:e36f::/48 498 | 2406:840:e500::/47 499 | 2406:840:e57b::/48 500 | 2406:840:e57f::/48 501 | 2406:840:e666::/47 502 | 2406:840:e720::/48 503 | 2406:840:e770::/48 504 | 2406:840:e777::/48 505 | 2406:840:e80f::/48 506 | 2406:840:e841::/48 507 | 2406:840:e842::/48 508 | 2406:840:e84a::/48 509 | 2406:840:e84c::/47 510 | 2406:840:e84f::/48 511 | 2406:840:e880::/44 512 | 2406:840:eab0::/46 513 | 2406:840:eab4::/48 514 | 2406:840:eab6::/48 515 | 2406:840:eb00::/46 516 | 2406:840:eb04::/47 517 | 2406:840:eb07::/48 518 | 2406:840:eb08::/47 519 | 2406:840:eb0b::/48 520 | 2406:840:eb0f::/48 521 | 2406:840:eb80::/42 522 | 2406:840:ed02::/48 523 | 2406:840:eed0::/44 524 | 2406:840:efa0::/44 525 | 2406:840:f0a1::/48 526 | 2406:840:f0aa::/48 527 | 2406:840:f230::/44 528 | 2406:840:f380::/44 529 | 2406:840:f401::/48 530 | 2406:840:f40f::/48 531 | 2406:840:f440::/47 532 | 2406:840:f44f::/48 533 | 2406:840:f48f::/48 534 | 2406:840:f660::/44 535 | 2406:840:f990::/44 536 | 2406:840:fa01::/48 537 | 2406:840:fa02::/47 538 | 2406:840:fa04::/48 539 | 2406:840:fd00::/47 540 | 2406:840:fd03::/48 541 | 2406:840:fd1f::/48 542 | 2406:840:fdd0::/47 543 | 2406:840:fde0::/48 544 | 2406:840:fe27::/48 545 | 2406:840:fe50::/48 546 | 2406:840:fe60::/48 547 | 2406:840:fe72::/47 548 | 2406:840:fec0::/47 549 | 2406:840:fec4::/47 550 | 2406:840:fec8::/45 551 | 2406:840:fed1::/48 552 | 2406:840:fed2::/47 553 | 2406:840:fed4::/46 554 | 2406:840:fed8::/47 555 | 2406:840:feda::/48 556 | 2406:840:fedd::/48 557 | 2406:840:fede::/47 558 | 2406:840:feed::/48 559 | 2406:840:fef0::/46 560 | 2406:840:fef5::/48 561 | 2406:840:fef7::/48 562 | 2406:840:fef8::/47 563 | 2406:840:fefb::/48 564 | 2406:840:fefc::/46 565 | 2406:1e40:f012::/47 566 | 2406:2000:a0::/48 567 | 2406:2700::/32 568 | 2406:3340::/32 569 | 2406:3640:1::/48 570 | 2406:3d80::/32 571 | 2406:4440:f000::/44 572 | 2406:4d00::/48 573 | 2406:52c0::/32 574 | 2406:5340:6666::/48 575 | 2406:5340:8888::/48 576 | 2406:5ac0::/32 577 | 2406:8880::/48 578 | 2406:94c0::/48 579 | 2406:b640:100::/48 580 | 2406:b640:4100::/48 581 | 2406:cf00::/48 582 | 2406:cf00:1000::/43 583 | 2406:d440:100::/44 584 | 2406:d440:200::/44 585 | 2406:d440:300::/44 586 | 2406:e3c0::/32 587 | 2406:e500::/33 588 | 2407:2840::/48 589 | 2407:3740::/48 590 | 2407:37c0::/32 591 | 2407:4f00::/47 592 | 2407:4f00:2::/48 593 | 2407:5380::/32 594 | 2407:6c40:1210::/48 595 | 2407:6c40:1500::/48 596 | 2407:6c40:1600::/40 597 | 2407:6c40:1810::/48 598 | 2407:8f40:2::/48 599 | 2407:9f00::/32 600 | 2407:ad80::/32 601 | 2407:ae80::/32 602 | 2407:b380::/33 603 | 2407:b380:8000::/48 604 | 2407:ba80::/32 605 | 2407:bc00::/32 606 | 2407:c080::/35 607 | 2407:c080:4000::/37 608 | 2407:c080:5000::/37 609 | 2407:c080:6000::/36 610 | 2407:c080:8000::/36 611 | 2408:4000::/22 612 | 2408:8000::/48 613 | 2408:8000:2::/47 614 | 2408:8000:1000::/36 615 | 2408:8000:2000::/35 616 | 2408:8000:4000::/34 617 | 2408:8000:8000::/33 618 | 2408:8001::/32 619 | 2408:8020::/30 620 | 2408:8024::/31 621 | 2408:8026::/32 622 | 2408:802a:8000::/33 623 | 2408:802c::/32 624 | 2408:8034::/32 625 | 2408:803e::/32 626 | 2408:8056::/32 627 | 2408:805c::/30 628 | 2408:8060::/33 629 | 2408:80c2::/33 630 | 2408:80c5::/33 631 | 2408:80ca::/33 632 | 2408:80ca:8000::/34 633 | 2408:80da::/33 634 | 2408:80da:8000::/34 635 | 2408:80e0:4000::/34 636 | 2408:80e0:8000::/33 637 | 2408:80e2::/33 638 | 2408:80e3:8000::/33 639 | 2408:80e9:4000::/34 640 | 2408:80ea:4000::/34 641 | 2408:80ea:8000::/33 642 | 2408:80f0:4000::/34 643 | 2408:80f0:8000::/33 644 | 2408:80f1::/42 645 | 2408:80f1:40::/43 646 | 2408:80f1:70::/44 647 | 2408:80f1:80::/41 648 | 2408:80f1:100::/43 649 | 2408:80f1:120::/44 650 | 2408:80f1:160::/43 651 | 2408:80f1:180::/43 652 | 2408:80f1:1b0::/44 653 | 2408:80f1:1c0::/43 654 | 2408:80f1:1e0::/44 655 | 2408:80f1:200::/40 656 | 2408:80f5:4000::/34 657 | 2408:80f9:4000::/34 658 | 2408:80fa:4000::/34 659 | 2408:80fa:8000::/33 660 | 2408:8120:1::/48 661 | 2408:8120:2::/48 662 | 2408:8120:7000::/36 663 | 2408:8140:2000::/48 664 | 2408:815f:e000::/35 665 | 2408:81a2:2000::/35 666 | 2408:81a2:4000::/35 667 | 2408:81a3:6000::/35 668 | 2408:81a3:c800::/48 669 | 2408:81a3:ca66::/48 670 | 2408:8206::/31 671 | 2408:8208::/29 672 | 2408:8210::/30 673 | 2408:8214::/31 674 | 2408:821a::/31 675 | 2408:8220::/31 676 | 2408:8226::/32 677 | 2408:822a::/31 678 | 2408:822e::/31 679 | 2408:8230::/29 680 | 2408:8238::/31 681 | 2408:823c::/31 682 | 2408:8240::/32 683 | 2408:8244::/30 684 | 2408:8248::/30 685 | 2408:824c::/32 686 | 2408:824e::/31 687 | 2408:8250::/29 688 | 2408:8258::/30 689 | 2408:825c::/31 690 | 2408:8260::/32 691 | 2408:8262::/31 692 | 2408:8264::/31 693 | 2408:8266::/32 694 | 2408:826a::/32 695 | 2408:826c::/30 696 | 2408:8270::/32 697 | 2408:8274::/30 698 | 2408:8278::/31 699 | 2408:827a::/32 700 | 2408:8306::/31 701 | 2408:8308::/30 702 | 2408:8310::/30 703 | 2408:832e::/31 704 | 2408:8330::/30 705 | 2408:8338::/32 706 | 2408:8340::/32 707 | 2408:8344::/30 708 | 2408:8348::/30 709 | 2408:834e::/31 710 | 2408:8350::/29 711 | 2408:8358::/30 712 | 2408:8360::/30 713 | 2408:8364::/31 714 | 2408:836c::/30 715 | 2408:8374::/30 716 | 2408:8378::/31 717 | 2408:837a::/32 718 | 2408:8406::/40 719 | 2408:8406:100::/41 720 | 2408:8406:180::/42 721 | 2408:8406:c00::/40 722 | 2408:8406:d00::/41 723 | 2408:8406:d80::/42 724 | 2408:8406:1800::/40 725 | 2408:8406:1900::/41 726 | 2408:8406:1980::/42 727 | 2408:8406:2400::/40 728 | 2408:8406:2500::/41 729 | 2408:8406:2580::/42 730 | 2408:8406:3000::/40 731 | 2408:8406:3100::/41 732 | 2408:8406:3180::/42 733 | 2408:8406:3c00::/40 734 | 2408:8406:3d00::/41 735 | 2408:8406:3d80::/42 736 | 2408:8406:4800::/40 737 | 2408:8406:4900::/41 738 | 2408:8406:4980::/42 739 | 2408:8406:5400::/40 740 | 2408:8406:5500::/41 741 | 2408:8406:5580::/42 742 | 2408:8406:6000::/40 743 | 2408:8406:6100::/41 744 | 2408:8406:6180::/42 745 | 2408:8406:6c00::/40 746 | 2408:8406:6d00::/41 747 | 2408:8406:6d80::/42 748 | 2408:8406:7800::/40 749 | 2408:8406:7900::/41 750 | 2408:8406:7980::/42 751 | 2408:8406:8400::/40 752 | 2408:8406:8500::/41 753 | 2408:8406:8580::/42 754 | 2408:8406:9000::/40 755 | 2408:8406:9100::/41 756 | 2408:8406:9180::/42 757 | 2408:8406:9c00::/40 758 | 2408:8406:9d00::/41 759 | 2408:8406:9d80::/42 760 | 2408:8406:a800::/40 761 | 2408:8406:a900::/41 762 | 2408:8406:a980::/42 763 | 2408:8406:b400::/40 764 | 2408:8406:b500::/41 765 | 2408:8406:b580::/42 766 | 2408:8409::/40 767 | 2408:8409:120::/43 768 | 2408:8409:1a0::/43 769 | 2408:8409:c00::/40 770 | 2408:8409:d00::/42 771 | 2408:8409:1800::/40 772 | 2408:8409:1900::/42 773 | 2408:8409:2400::/40 774 | 2408:8409:2500::/42 775 | 2408:8409:3000::/40 776 | 2408:8409:3100::/42 777 | 2408:8409:3c00::/40 778 | 2408:8409:3d00::/42 779 | 2408:8409:4800::/40 780 | 2408:8409:4900::/42 781 | 2408:8409:5400::/40 782 | 2408:8409:5500::/42 783 | 2408:8409:6000::/40 784 | 2408:8409:6100::/42 785 | 2408:8409:6c00::/40 786 | 2408:8409:6d00::/42 787 | 2408:8409:7800::/40 788 | 2408:8409:7900::/42 789 | 2408:8409:8400::/40 790 | 2408:8409:8500::/42 791 | 2408:8409:9000::/40 792 | 2408:8409:9100::/42 793 | 2408:8409:9c00::/40 794 | 2408:8409:9d00::/42 795 | 2408:8409:a800::/40 796 | 2408:8409:a900::/42 797 | 2408:8409:b400::/40 798 | 2408:8409:b500::/42 799 | 2408:840c::/40 800 | 2408:840c:200::/40 801 | 2408:840c:400::/40 802 | 2408:840c:d00::/40 803 | 2408:840c:f00::/40 804 | 2408:840c:1100::/40 805 | 2408:840c:1a00::/40 806 | 2408:840c:1c00::/40 807 | 2408:840c:1e00::/40 808 | 2408:840c:2700::/40 809 | 2408:840c:2900::/40 810 | 2408:840c:2b00::/40 811 | 2408:840c:3400::/40 812 | 2408:840c:3600::/40 813 | 2408:840c:3800::/40 814 | 2408:840c:4e00::/40 815 | 2408:840c:5000::/40 816 | 2408:840c:5200::/40 817 | 2408:840c:5b00::/40 818 | 2408:840c:5d00::/40 819 | 2408:840c:5f00::/40 820 | 2408:840c:6800::/40 821 | 2408:840c:6a00::/40 822 | 2408:840c:6c00::/40 823 | 2408:840c:7500::/40 824 | 2408:840c:7700::/40 825 | 2408:840c:7900::/40 826 | 2408:840c:8200::/40 827 | 2408:840c:8400::/40 828 | 2408:840c:8600::/40 829 | 2408:840c:8f00::/40 830 | 2408:840c:9100::/40 831 | 2408:840c:9300::/40 832 | 2408:840c:9c00::/40 833 | 2408:840c:9e00::/40 834 | 2408:840c:a000::/40 835 | 2408:840c:a900::/40 836 | 2408:840c:ab00::/40 837 | 2408:840c:ad00::/40 838 | 2408:840c:b600::/40 839 | 2408:840c:b800::/40 840 | 2408:840c:ba00::/40 841 | 2408:840c:c300::/40 842 | 2408:840c:c500::/40 843 | 2408:840c:c700::/40 844 | 2408:840c:d000::/40 845 | 2408:840c:d200::/40 846 | 2408:840c:d400::/40 847 | 2408:840c:dd00::/40 848 | 2408:840c:de00::/39 849 | 2408:840d::/42 850 | 2408:840d:200::/42 851 | 2408:840d:400::/42 852 | 2408:840d:600::/42 853 | 2408:840d:d00::/42 854 | 2408:840d:f00::/42 855 | 2408:840d:1100::/42 856 | 2408:840d:1300::/42 857 | 2408:840d:1a00::/42 858 | 2408:840d:1c00::/42 859 | 2408:840d:1e00::/42 860 | 2408:840d:2000::/42 861 | 2408:840d:2700::/42 862 | 2408:840d:2900::/42 863 | 2408:840d:2b00::/42 864 | 2408:840d:2d00::/42 865 | 2408:840d:3400::/42 866 | 2408:840d:3600::/42 867 | 2408:840d:3800::/42 868 | 2408:840d:3a00::/42 869 | 2408:840d:4e00::/42 870 | 2408:840d:5000::/42 871 | 2408:840d:5200::/42 872 | 2408:840d:5400::/42 873 | 2408:840d:5b00::/42 874 | 2408:840d:5d00::/42 875 | 2408:840d:5f00::/42 876 | 2408:840d:6100::/42 877 | 2408:840d:6800::/42 878 | 2408:840d:6a00::/42 879 | 2408:840d:6c00::/42 880 | 2408:840d:6e00::/42 881 | 2408:840d:7500::/42 882 | 2408:840d:7700::/42 883 | 2408:840d:7900::/42 884 | 2408:840d:7b00::/42 885 | 2408:840d:8200::/42 886 | 2408:840d:8400::/42 887 | 2408:840d:8600::/42 888 | 2408:840d:8800::/42 889 | 2408:840d:8f00::/42 890 | 2408:840d:9100::/42 891 | 2408:840d:9300::/42 892 | 2408:840d:9500::/42 893 | 2408:840d:9c00::/42 894 | 2408:840d:9e00::/42 895 | 2408:840d:a000::/42 896 | 2408:840d:a200::/42 897 | 2408:840d:a900::/42 898 | 2408:840d:ab00::/42 899 | 2408:840d:ad00::/42 900 | 2408:840d:af00::/42 901 | 2408:840d:b600::/42 902 | 2408:840d:b800::/42 903 | 2408:840d:ba00::/42 904 | 2408:840d:bc00::/42 905 | 2408:840d:c300::/42 906 | 2408:840d:c500::/42 907 | 2408:840d:c700::/42 908 | 2408:840d:c900::/42 909 | 2408:840d:d000::/42 910 | 2408:840d:d200::/42 911 | 2408:840d:d400::/42 912 | 2408:840d:d600::/42 913 | 2408:840d:dd00::/42 914 | 2408:840d:de00::/42 915 | 2408:840e:dd00::/40 916 | 2408:840e:de00::/39 917 | 2408:8410::/30 918 | 2408:8414::/31 919 | 2408:8417::/32 920 | 2408:8418::/32 921 | 2408:841a::/31 922 | 2408:841c::/31 923 | 2408:841e::/32 924 | 2408:8420::/31 925 | 2408:8422::/32 926 | 2408:8426::/31 927 | 2408:842a::/31 928 | 2408:842c::/32 929 | 2408:842e::/32 930 | 2408:8431::/32 931 | 2408:8434::/30 932 | 2408:8438::/31 933 | 2408:843c::/30 934 | 2408:8440::/31 935 | 2408:8444::/30 936 | 2408:8448::/32 937 | 2408:844b::/32 938 | 2408:844c::/30 939 | 2408:8452::/31 940 | 2408:8454::/32 941 | 2408:8456::/31 942 | 2408:8458::/30 943 | 2408:845c::/31 944 | 2408:8460::/30 945 | 2408:8464::/31 946 | 2408:8466::/32 947 | 2408:8469::/32 948 | 2408:846a::/31 949 | 2408:846c::/30 950 | 2408:8470::/31 951 | 2408:8474::/30 952 | 2408:8478::/31 953 | 2408:847a::/32 954 | 2408:84e1::/32 955 | 2408:84e2::/31 956 | 2408:84e4::/30 957 | 2408:84e9::/32 958 | 2408:84eb::/32 959 | 2408:84ec::/30 960 | 2408:84f0::/28 961 | 2408:856c::/31 962 | 2408:8606::/31 963 | 2408:8608::/29 964 | 2408:8610::/30 965 | 2408:8614::/31 966 | 2408:861a::/31 967 | 2408:861c::/32 968 | 2408:8620::/31 969 | 2408:8624::/31 970 | 2408:8626::/32 971 | 2408:862a::/31 972 | 2408:862d::/32 973 | 2408:862e::/31 974 | 2408:8630::/29 975 | 2408:8638::/31 976 | 2408:863c::/31 977 | 2408:8640::/32 978 | 2408:8642::/32 979 | 2408:8644::/30 980 | 2408:8648::/31 981 | 2408:864c::/32 982 | 2408:864e::/31 983 | 2408:8650::/30 984 | 2408:8654::/32 985 | 2408:8656::/31 986 | 2408:8658::/30 987 | 2408:865c::/31 988 | 2408:8660::/32 989 | 2408:8662::/31 990 | 2408:8664::/31 991 | 2408:8666::/32 992 | 2408:866a::/31 993 | 2408:866c::/30 994 | 2408:8670::/32 995 | 2408:8674::/30 996 | 2408:8678::/31 997 | 2408:867a::/32 998 | 2408:8706::/31 999 | 2408:8708::/29 1000 | 2408:8710::/30 1001 | 2408:8719::/32 1002 | 2408:871a::/31 1003 | 2408:8720::/30 1004 | 2408:8726::/32 1005 | 2408:872b::/32 1006 | 2408:872f::/32 1007 | 2408:8730::/30 1008 | 2408:8734::/31 1009 | 2408:8736::/32 1010 | 2408:8738::/32 1011 | 2408:873c::/31 1012 | 2408:8740::/32 1013 | 2408:8742::/32 1014 | 2408:8744::/30 1015 | 2408:8748::/29 1016 | 2408:8752::/32 1017 | 2408:8756::/31 1018 | 2408:8758::/30 1019 | 2408:875c::/32 1020 | 2408:8760::/32 1021 | 2408:8762::/31 1022 | 2408:8764::/31 1023 | 2408:8766::/32 1024 | 2408:8768::/32 1025 | 2408:876a::/32 1026 | 2408:876c::/30 1027 | 2408:8770::/32 1028 | 2408:8772::/31 1029 | 2408:8774::/32 1030 | 2408:8776::/31 1031 | 2408:8778::/31 1032 | 2408:877a::/32 1033 | 2408:877c::/30 1034 | 2408:8806::/42 1035 | 2408:8806:40::/43 1036 | 2408:880c::/30 1037 | 2408:8810::/30 1038 | 2408:8814::/31 1039 | 2408:8816::/32 1040 | 2408:8818::/31 1041 | 2408:882c::/32 1042 | 2408:883a::/32 1043 | 2408:8844::/43 1044 | 2408:8856::/31 1045 | 2408:8858::/30 1046 | 2408:8862::/31 1047 | 2408:8864::/31 1048 | 2408:8866::/32 1049 | 2408:886e::/31 1050 | 2408:8872::/32 1051 | 2408:8878::/31 1052 | 2408:887e::/32 1053 | 2408:8906:20::/44 1054 | 2408:8907:9000::/44 1055 | 2408:890c::/31 1056 | 2408:8912::/31 1057 | 2408:8914::/30 1058 | 2408:891c::/32 1059 | 2408:8920::/32 1060 | 2408:8924::/32 1061 | 2408:892c::/32 1062 | 2408:8936::/32 1063 | 2408:893a::/32 1064 | 2408:8940::/32 1065 | 2408:8948::/32 1066 | 2408:894c::/32 1067 | 2408:894e::/32 1068 | 2408:8956::/31 1069 | 2408:8958::/30 1070 | 2408:8962::/31 1071 | 2408:8964::/31 1072 | 2408:8966::/32 1073 | 2408:896c::/30 1074 | 2408:8978::/30 1075 | 2408:897e::/32 1076 | 2408:8a00:c000::/36 1077 | 2408:8a00:d000::/37 1078 | 2408:8a00:e000::/35 1079 | 2408:8a01::/36 1080 | 2408:8a02:b110::/44 1081 | 2408:8a02:b120::/44 1082 | 2408:8a04:8000::/36 1083 | 2408:8a04:e000::/40 1084 | 2408:8a05:6000::/35 1085 | 2408:8a05:8000::/36 1086 | 2408:8a06::/47 1087 | 2408:8a06:100::/47 1088 | 2408:8a07:6000::/38 1089 | 2408:8a07:6400::/40 1090 | 2408:8a21:4000::/35 1091 | 2408:8a22:9200::/39 1092 | 2408:8a22:9400::/38 1093 | 2408:8a22:9800::/40 1094 | 2408:8a22:9a00::/39 1095 | 2408:8a22:9c00::/38 1096 | 2408:8a22:a000::/37 1097 | 2408:8a23:4000::/34 1098 | 2408:8a24:4000::/34 1099 | 2408:8a26:c000::/34 1100 | 2408:8a27:4000::/35 1101 | 2409:2000::/31 1102 | 2409:27fa::/48 1103 | 2409:27fa:f000::/48 1104 | 2409:27fb::/48 1105 | 2409:27fc::/48 1106 | 2409:27fe::/33 1107 | 2409:6100::/44 1108 | 2409:8000::/20 1109 | 240a:2000::/29 1110 | 240a:4010:8000::/33 1111 | 240a:4020:83a::/48 1112 | 240a:4020:883a::/48 1113 | 240a:4021:83a::/48 1114 | 240a:4021:883a::/48 1115 | 240a:4084:2000::/35 1116 | 240a:408c:2000::/35 1117 | 240a:4090:50::/48 1118 | 240a:4090:120::/48 1119 | 240a:4090:250::/48 1120 | 240a:4090:1000::/39 1121 | 240a:4090:1200::/40 1122 | 240a:4090:2010::/48 1123 | 240a:4090:2041::/48 1124 | 240a:4090:2061::/48 1125 | 240a:4090:3000::/39 1126 | 240a:4090:3200::/40 1127 | 240a:4090:5000::/39 1128 | 240a:4090:5200::/40 1129 | 240a:4090:7000::/39 1130 | 240a:4090:7200::/40 1131 | 240a:4094:2000::/35 1132 | 240a:409c:2000::/35 1133 | 240a:40a4:2000::/35 1134 | 240a:40ac:2000::/35 1135 | 240a:40b0:83a::/48 1136 | 240a:40b0:283a::/48 1137 | 240a:40b0:483a::/48 1138 | 240a:40b0:683a::/48 1139 | 240a:40c0:8200::/48 1140 | 240a:40c0:8240::/48 1141 | 240a:40c3:c200::/48 1142 | 240a:40c3:c240::/48 1143 | 240a:4172::/31 1144 | 240a:41b0::/31 1145 | 240a:41f2::/31 1146 | 240a:4242::/31 1147 | 240a:4280::/26 1148 | 240a:42c0::/27 1149 | 240a:42e0::/28 1150 | 240a:42f0::/29 1151 | 240a:42f8::/30 1152 | 240a:6001::/48 1153 | 240a:a000::/20 1154 | 240a:c000::/20 1155 | 240b:e001::/32 1156 | 240b:e002::/31 1157 | 240b:e004::/30 1158 | 240b:e008::/29 1159 | 240b:e010::/32 1160 | 240c::/28 1161 | 240c:4000::/22 1162 | 240c:c000::/20 1163 | 240d:4000::/21 1164 | 240d:c000:1000::/36 1165 | 240d:c000:2000::/35 1166 | 240d:c000:6000::/36 1167 | 240d:c000:7000::/44 1168 | 240d:c000:f000::/44 1169 | 240d:c000:f020::/44 1170 | 240d:c010::/47 1171 | 240d:c010:14::/48 1172 | 240d:c010:16::/48 1173 | 240d:c010:20::/44 1174 | 240d:c010:30::/47 1175 | 240d:c010:58::/48 1176 | 240d:c010:5b::/48 1177 | 240d:c010:5c::/48 1178 | 240d:c010:68::/48 1179 | 240d:c010:6c::/48 1180 | 240d:c040::/44 1181 | 240e::/20 1182 | 2602:2a3::/48 1183 | 2602:2a4:ff::/48 1184 | 2602:2c3:810::/44 1185 | 2602:f9df:400::/47 1186 | 2602:f9df:500::/48 1187 | 2602:f9df:505::/48 1188 | 2602:f9df:515::/48 1189 | 2602:f9df:516::/48 1190 | 2602:f9f6:400::/47 1191 | 2602:fa4f:600::/40 1192 | 2602:fab0:11::/48 1193 | 2602:fbda:600::/48 1194 | 2602:fbda:660::/48 1195 | 2602:fbda:666::/48 1196 | 2602:fd92:801::/48 1197 | 2602:fe69:f26::/47 1198 | 2602:fed2:731d::/48 1199 | 2602:feda:182::/47 1200 | 2602:feda:1bf::/48 1201 | 2602:feda:1d1::/48 1202 | 2602:feda:1d2::/48 1203 | 2602:feda:2d0::/47 1204 | 2602:feda:2f0::/48 1205 | 2602:feda:3c5::/48 1206 | 2602:feda:c34::/48 1207 | 2602:feda:d80::/48 1208 | 2602:feda:d83::/48 1209 | 2602:ffe4:c5f::/48 1210 | 2602:ffe4:c60::/47 1211 | 2605:9d80:8001::/48 1212 | 2605:9d80:8011::/48 1213 | 2605:9d80:8021::/48 1214 | 2605:9d80:8031::/48 1215 | 2605:9d80:8041::/48 1216 | 2605:9d80:8081::/48 1217 | 2605:9d80:9003::/48 1218 | 2605:9d80:9013::/48 1219 | 2605:9d80:9023::/48 1220 | 2605:9d80:9033::/48 1221 | 2605:9d80:9042::/48 1222 | 2605:9d80:9052::/48 1223 | 2605:9d80:9071::/48 1224 | 2605:9d80:9092::/48 1225 | 2620:57:4004::/47 1226 | 2804:1e48::/32 1227 | 2a03:5840:290::/48 1228 | 2a04:3e00:1002::/48 1229 | 2a04:f580:8010::/47 1230 | 2a04:f580:8090::/48 1231 | 2a04:f580:8210::/47 1232 | 2a04:f580:8290::/48 1233 | 2a04:f580:9000::/47 1234 | 2a04:f580:9002::/48 1235 | 2a04:f580:9010::/48 1236 | 2a04:f580:9012::/47 1237 | 2a04:f580:9020::/48 1238 | 2a04:f580:9030::/48 1239 | 2a04:f580:9040::/48 1240 | 2a04:f580:9050::/48 1241 | 2a04:f580:9060::/48 1242 | 2a04:f580:9070::/48 1243 | 2a04:f580:9080::/48 1244 | 2a04:f580:9090::/48 1245 | 2a04:f580:9200::/47 1246 | 2a04:f580:9202::/48 1247 | 2a04:f580:9210::/48 1248 | 2a04:f580:9212::/47 1249 | 2a04:f580:9220::/48 1250 | 2a04:f580:9230::/48 1251 | 2a04:f580:9240::/48 1252 | 2a04:f580:9250::/48 1253 | 2a04:f580:9260::/48 1254 | 2a04:f580:9270::/48 1255 | 2a04:f580:9280::/48 1256 | 2a04:f580:9290::/48 1257 | 2a05:1085::/32 1258 | 2a05:1087::/32 1259 | 2a05:dfc1:1600::/40 1260 | 2a05:dfc1:7106::/47 1261 | 2a05:dfc1:7108::/47 1262 | 2a05:dfc1:7110::/48 1263 | 2a05:dfc1:8c02::/48 1264 | 2a05:dfc1:8c05::/48 1265 | 2a05:dfc1:8c09::/48 1266 | 2a05:dfc1:8c0a::/48 1267 | 2a05:dfc1:8c13::/48 1268 | 2a05:dfc1:8c16::/48 1269 | 2a05:dfc1:8c1b::/48 1270 | 2a05:dfc1:8c1d::/48 1271 | 2a05:dfc1:8c22::/48 1272 | 2a05:dfc1:8c24::/48 1273 | 2a05:dfc1:8c42::/48 1274 | 2a05:dfc1:8c90::/46 1275 | 2a05:dfc1:8c94::/47 1276 | 2a05:dfc1:8d00::/48 1277 | 2a05:dfc1:8d02::/48 1278 | 2a06:3600::/29 1279 | 2a06:a005:260::/43 1280 | 2a06:a005:280::/43 1281 | 2a06:a005:2a0::/44 1282 | 2a06:a005:9c0::/48 1283 | 2a06:a005:a13::/48 1284 | 2a06:a005:b69::/48 1285 | 2a06:a005:e80::/43 1286 | 2a06:a005:f80::/44 1287 | 2a06:a005:1340::/43 1288 | 2a06:a005:13b0::/44 1289 | 2a06:a005:13c0::/43 1290 | 2a06:a005:13e0::/47 1291 | 2a06:a005:13e2::/48 1292 | 2a06:a005:13ea::/47 1293 | 2a06:a005:13ed::/48 1294 | 2a06:a005:13ef::/48 1295 | 2a06:a005:1ee1::/48 1296 | 2a06:a005:1ee9::/48 1297 | 2a06:a005:1eee::/48 1298 | 2a06:a005:2040::/44 1299 | 2a06:a005:28f3::/48 1300 | 2a06:a005:28f4::/48 1301 | 2a06:a005:2910::/44 1302 | 2a06:a005:2922::/48 1303 | 2a06:a005:2926::/48 1304 | 2a06:a005:2940::/43 1305 | 2a06:a005:2980::/44 1306 | 2a06:a005:29d1::/48 1307 | 2a06:de00:de04::/48 1308 | 2a06:de00:de0e::/48 1309 | 2a06:e881:6600::/48 1310 | 2a06:e881:6602::/47 1311 | 2a06:e881:6606::/48 1312 | 2a07:54c1:2200::/47 1313 | 2a07:54c1:2202::/48 1314 | 2a09:b280:ff80::/48 1315 | 2a09:b280:ff83::/48 1316 | 2a09:b280:ff84::/47 1317 | 2a0a:2840:20::/43 1318 | 2a0a:2845:aab8::/46 1319 | 2a0a:6040:c00::/40 1320 | 2a0a:6040:e01::/48 1321 | 2a0a:6040:e02::/48 1322 | 2a0a:6040:ea0::/48 1323 | 2a0a:6040:f00::/47 1324 | 2a0a:6040:f02::/48 1325 | 2a0a:6040:f11::/48 1326 | 2a0a:6040:f15::/48 1327 | 2a0a:6040:1b00::/40 1328 | 2a0a:6040:2222::/48 1329 | 2a0a:6040:2228::/48 1330 | 2a0a:6040:2233::/48 1331 | 2a0a:6040:2255::/48 1332 | 2a0a:6040:2277::/48 1333 | 2a0a:6040:2d00::/44 1334 | 2a0a:6040:3410::/48 1335 | 2a0a:6040:3420::/48 1336 | 2a0a:6040:3430::/48 1337 | 2a0a:6040:34ff::/48 1338 | 2a0a:6040:5555::/48 1339 | 2a0a:6040:6607::/48 1340 | 2a0a:6040:6c40::/44 1341 | 2a0a:6040:a450::/47 1342 | 2a0a:6040:a740::/48 1343 | 2a0a:6040:a900::/47 1344 | 2a0a:6040:b770::/44 1345 | 2a0a:6040:c601::/48 1346 | 2a0a:6040:d310::/48 1347 | 2a0a:6040:d600::/44 1348 | 2a0a:6040:d610::/48 1349 | 2a0a:6040:d612::/48 1350 | 2a0a:6040:d614::/47 1351 | 2a0a:6040:d617::/48 1352 | 2a0a:6040:e410::/48 1353 | 2a0a:6040:e541::/48 1354 | 2a0a:6040:e543::/48 1355 | 2a0a:6040:e544::/47 1356 | 2a0a:6040:e54c::/48 1357 | 2a0a:6040:e900::/40 1358 | 2a0b:b87:ffb5::/48 1359 | 2a0b:2542::/48 1360 | 2a0b:4340:30::/44 1361 | 2a0b:4340:70::/48 1362 | 2a0b:4340:90::/48 1363 | 2a0b:4340:93::/48 1364 | 2a0b:4340:95::/48 1365 | 2a0b:4340:97::/48 1366 | 2a0b:4340:99::/48 1367 | 2a0b:4340:9e::/48 1368 | 2a0b:4340:a0::/44 1369 | 2a0b:4340:c0::/44 1370 | 2a0b:4340:d8::/48 1371 | 2a0b:4340:560::/44 1372 | 2a0c:9a40:8cf0::/48 1373 | 2a0c:9a40:9e00::/43 1374 | 2a0c:b641:210::/47 1375 | 2a0c:b641:24f::/48 1376 | 2a0c:b641:510::/48 1377 | 2a0c:b641:570::/47 1378 | 2a0c:b641:a60::/44 1379 | 2a0c:b642:4101::/48 1380 | 2a0c:b642:4102::/47 1381 | 2a0c:b642:4104::/46 1382 | 2a0d:2580:ff00::/45 1383 | 2a0d:2580:ff08::/46 1384 | 2a0d:2581:fffc::/48 1385 | 2a0d:2581:fffe::/47 1386 | 2a0d:2687::/32 1387 | 2a0d:2904::/44 1388 | 2a0d:c7c7::/32 1389 | 2a0e:800:ff20::/47 1390 | 2a0e:800:ff40::/42 1391 | 2a0e:15c0:6::/48 1392 | 2a0e:8f02:2182::/47 1393 | 2a0e:8f02:f006::/48 1394 | 2a0e:8f02:f058::/48 1395 | 2a0e:8f02:f062::/48 1396 | 2a0e:8f02:f067::/48 1397 | 2a0e:97c0:220::/44 1398 | 2a0e:97c0:550::/44 1399 | 2a0e:97c0:5ef::/48 1400 | 2a0e:97c0:83f::/48 1401 | 2a0e:9b00::/29 1402 | 2a0e:aa01:1fff::/48 1403 | 2a0e:aa06::/40 1404 | 2a0e:aa06:400::/44 1405 | 2a0e:aa06:440::/48 1406 | 2a0e:aa06:470::/44 1407 | 2a0e:aa06:490::/44 1408 | 2a0e:aa07:e01b::/48 1409 | 2a0e:aa07:e024::/47 1410 | 2a0e:aa07:e027::/48 1411 | 2a0e:aa07:e030::/48 1412 | 2a0e:aa07:e043::/48 1413 | 2a0e:aa07:e050::/44 1414 | 2a0e:aa07:e060::/48 1415 | 2a0e:aa07:e071::/48 1416 | 2a0e:aa07:e120::/44 1417 | 2a0e:aa07:e130::/47 1418 | 2a0e:aa07:e140::/47 1419 | 2a0e:aa07:e143::/48 1420 | 2a0e:aa07:e145::/48 1421 | 2a0e:aa07:e146::/47 1422 | 2a0e:aa07:e16a::/48 1423 | 2a0e:aa07:e1a0::/46 1424 | 2a0e:aa07:e1b0::/47 1425 | 2a0e:aa07:e1b2::/48 1426 | 2a0e:aa07:e1b4::/47 1427 | 2a0e:aa07:e200::/44 1428 | 2a0e:aa07:f000::/48 1429 | 2a0e:aa07:f004::/48 1430 | 2a0e:aa07:f008::/48 1431 | 2a0e:aa07:f041::/48 1432 | 2a0e:aa07:f042::/48 1433 | 2a0e:aa07:f0d0::/48 1434 | 2a0e:aa07:f0d2::/48 1435 | 2a0e:aa07:f0d4::/47 1436 | 2a0e:b107:30::/48 1437 | 2a0e:b107:32::/47 1438 | 2a0e:b107:a0::/44 1439 | 2a0e:b107:12b::/48 1440 | 2a0e:b107:1c1::/48 1441 | 2a0e:b107:272::/48 1442 | 2a0e:b107:740::/44 1443 | 2a0e:b107:c10::/48 1444 | 2a0e:b107:da0::/44 1445 | 2a0e:b107:dce::/48 1446 | 2a0e:b107:14a0::/44 1447 | 2a0e:b107:16b0::/44 1448 | 2a0e:b107:16c0::/44 1449 | 2a0e:b107:1a40::/46 1450 | 2a0e:b107:1b60::/48 1451 | 2a0e:b107:1b6e::/47 1452 | 2a0e:b107:1e85::/48 1453 | 2a0e:b107:2440::/44 1454 | 2a0f:1440::/29 1455 | 2a0f:2100::/29 1456 | 2a0f:5707:ac01::/48 1457 | 2a0f:5707:fe01::/48 1458 | 2a0f:5707:fff4::/47 1459 | 2a0f:7803:fc00::/43 1460 | 2a0f:7803:fc20::/44 1461 | 2a0f:7803:fc80::/44 1462 | 2a0f:7803:fcc0::/44 1463 | 2a0f:7803:fe21::/48 1464 | 2a0f:7803:fe22::/48 1465 | 2a0f:7803:fe24::/48 1466 | 2a0f:7803:fe80::/47 1467 | 2a0f:7803:fe82::/48 1468 | 2a0f:7803:ff10::/48 1469 | 2a0f:7980::/44 1470 | 2a0f:85c1:3b5::/48 1471 | 2a0f:9400:6110::/48 1472 | 2a0f:9400:8016::/48 1473 | 2a0f:e401:133::/48 1474 | 2a10:2f00:147::/48 1475 | 2a10:2f00:15a::/48 1476 | 2a10:cc40:190::/48 1477 | 2a10:cc42:120::/43 1478 | 2a10:ccc0:cc1::/48 1479 | 2a10:ccc0:cc2::/48 1480 | 2a10:ccc0:ccc::/48 1481 | 2a10:ccc0:ccc6::/48 1482 | 2a10:ccc0:cccc::/46 1483 | 2a11:f2c0:ffc4::/48 1484 | 2a12:3fc2:6600::/48 1485 | 2a12:3fc2:6666::/48 1486 | 2a12:3fc2:ab50::/48 1487 | 2a12:3fc2:e72e::/48 1488 | 2a12:3fc2:e810::/44 1489 | 2a12:dd47:5c00::/40 1490 | 2a12:f8c0:1000::/40 1491 | 2a12:f8c3::/36 1492 | 2a13:1800::/48 1493 | 2a13:1800:10::/48 1494 | 2a13:1800:300::/44 1495 | 2a13:1801:180::/43 1496 | 2a13:1802::/43 1497 | 2a13:a5c3:f000::/40 1498 | 2a13:a5c4:1000::/38 1499 | 2a13:a5c7:1200::/40 1500 | 2a13:a5c7:1500::/40 1501 | 2a13:a5c7:1600::/45 1502 | 2a13:a5c7:1608::/47 1503 | 2a13:a5c7:1610::/46 1504 | 2a13:a5c7:1614::/47 1505 | 2a13:a5c7:1801::/48 1506 | 2a13:a5c7:1802::/47 1507 | 2a13:a5c7:1804::/47 1508 | 2a13:a5c7:2102::/47 1509 | 2a13:a5c7:2104::/48 1510 | 2a13:a5c7:2106::/48 1511 | 2a13:aac4:f000::/44 1512 | 2a13:b487:11da::/48 1513 | 2a13:b487:1200::/43 1514 | 2a13:b487:1ba2::/48 1515 | 2a13:b487:1ba6::/47 1516 | 2a13:b487:1bb2::/47 1517 | 2a13:b487:1bba::/47 1518 | 2a13:b487:1bc0::/47 1519 | 2a13:b487:1bff::/48 1520 | 2a13:b487:1f02::/47 1521 | 2a13:b487:1f04::/46 1522 | 2a13:df80:3b00::/44 1523 | 2a13:df80:3b11::/48 1524 | 2a13:df85:be10::/48 1525 | 2a13:df85:be80::/48 1526 | 2a13:df85:be88::/48 1527 | 2a13:df85:be90::/48 1528 | 2a13:df85:beaa::/48 1529 | 2a13:df85:bed0::/48 1530 | 2a13:df85:bedc::/48 1531 | 2a13:df85:befe::/48 1532 | 2c0f:f7a8:8011::/48 1533 | 2c0f:f7a8:8050::/48 1534 | 2c0f:f7a8:805f::/48 1535 | 2c0f:f7a8:8150::/48 1536 | 2c0f:f7a8:815f::/48 1537 | 2c0f:f7a8:8211::/48 1538 | 2c0f:f7a8:9010::/47 1539 | 2c0f:f7a8:9020::/48 1540 | 2c0f:f7a8:9041::/48 1541 | 2c0f:f7a8:9210::/47 1542 | 2c0f:f7a8:9220::/48 1543 | -------------------------------------------------------------------------------- /root/etc/homeproxy/resources/china_ip6.ver: -------------------------------------------------------------------------------- 1 | 20240602150004 2 | -------------------------------------------------------------------------------- /root/etc/homeproxy/resources/china_list.ver: -------------------------------------------------------------------------------- 1 | 202406062209 2 | -------------------------------------------------------------------------------- /root/etc/homeproxy/resources/gfw_list.ver: -------------------------------------------------------------------------------- 1 | 202406062209 2 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/clean_log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # 4 | # Copyright (C) 2022-2023 ImmortalWrt.org 5 | 6 | NAME="homeproxy" 7 | 8 | log_max_size="10" #KB 9 | main_log_file="/var/run/$NAME/$NAME.log" 10 | singc_log_file="/var/run/$NAME/sing-box-c.log" 11 | sings_log_file="/var/run/$NAME/sing-box-s.log" 12 | 13 | while true; do 14 | sleep 180 15 | for i in "$main_log_file" "$singc_log_file" "$sings_log_file"; do 16 | [ -s "$i" ] || continue 17 | [ "$(( $(ls -l "$i" | awk -F ' ' '{print $5}') / 1024 >= log_max_size))" -eq "0" ] || echo "" > "$i" 18 | done 19 | done 20 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/firewall_post.ut: -------------------------------------------------------------------------------- 1 | #!/usr/bin/utpl 2 | 3 | {%- 4 | 'use strict'; 5 | 6 | import { readfile } from 'fs'; 7 | import { cursor } from 'uci'; 8 | import { isEmpty } from '/etc/homeproxy/scripts/homeproxy.uc'; 9 | 10 | const fw4 = require('fw4'); 11 | 12 | function array_to_nftarr(array) { 13 | if (type(array) !== 'array') 14 | return null; 15 | 16 | return `{ ${join(', ', uniq(array))} }`; 17 | } 18 | 19 | function resolve_ipv6(str) { 20 | if (isEmpty(str)) 21 | return null; 22 | 23 | let ipv6 = fw4.parse_subnet(str)?.[0]; 24 | if (!ipv6 || ipv6.family !== 6) 25 | return null; 26 | 27 | if (ipv6.bits > -1) 28 | return `${ipv6.addr}/${ipv6.bits}`; 29 | else 30 | return `& ${ipv6.mask} == ${ipv6.addr}`; 31 | } 32 | 33 | /* Misc config */ 34 | const resources_dir = '/etc/homeproxy/resources'; 35 | 36 | /* UCI config start */ 37 | const cfgname = 'homeproxy'; 38 | const uci = cursor(); 39 | uci.load(cfgname); 40 | 41 | const routing_mode = uci.get(cfgname, 'config', 'routing_mode') || 'bypass_mainland_china'; 42 | let outbound_node, outbound_udp_node, china_dns_server, bypass_cn_traffic; 43 | 44 | if (routing_mode !== 'custom') { 45 | outbound_node = uci.get(cfgname, 'config', 'main_node') || 'nil'; 46 | outbound_udp_node = uci.get(cfgname, 'config', 'main_udp_node') || 'nil'; 47 | china_dns_server = uci.get(cfgname, 'config', 'china_dns_server'); 48 | } else { 49 | outbound_node = uci.get(cfgname, 'routing', 'default_outbound') || 'nil'; 50 | bypass_cn_traffic = uci.get(cfgname, 'routing', 'bypass_cn_traffic') || '0'; 51 | } 52 | 53 | let routing_port = uci.get(cfgname, 'config', 'routing_port') || 'common'; 54 | if (routing_port === 'common') 55 | routing_port = uci.get(cfgname, 'infra', 'common_port') || '22,53,80,143,443,465,587,853,873,993,995,8080,8443,9418'; 56 | 57 | const proxy_mode = uci.get(cfgname, 'config', 'proxy_mode') || 'redirect_tproxy', 58 | ipv6_support = uci.get(cfgname, 'config', 'ipv6_support') || '0'; 59 | 60 | let self_mark, redirect_port, 61 | tproxy_port, tproxy_mark, 62 | tun_name, tun_mark; 63 | 64 | if (match(proxy_mode, /redirect/)) { 65 | self_mark = uci.get(cfgname, 'infra', 'self_mark') || '100'; 66 | redirect_port = uci.get(cfgname, 'infra', 'redirect_port') || '5331'; 67 | } 68 | if (match(proxy_mode, /tproxy/)) 69 | if (outbound_udp_node !== 'nil' || routing_mode === 'custom') { 70 | tproxy_port = uci.get(cfgname, 'infra', 'tproxy_port') || '5332'; 71 | tproxy_mark = uci.get(cfgname, 'infra', 'tproxy_mark') || '101'; 72 | } 73 | if (match(proxy_mode, /tun/)) { 74 | tun_name = uci.get(cfgname, 'infra', 'tun_name') || 'singtun0'; 75 | tun_mark = uci.get(cfgname, 'infra', 'tun_mark') || '102'; 76 | } 77 | 78 | const control_options = [ 79 | "listen_interfaces", "lan_proxy_mode", 80 | "lan_direct_mac_addrs", "lan_direct_ipv4_ips", "lan_direct_ipv6_ips", 81 | "lan_proxy_mac_addrs", "lan_proxy_ipv4_ips", "lan_proxy_ipv6_ips", 82 | "lan_gaming_mode_mac_addrs", "lan_gaming_mode_ipv4_ips", "lan_gaming_mode_ipv6_ips", 83 | "lan_global_proxy_mac_addrs", "lan_global_proxy_ipv4_ips", "lan_global_proxy_ipv6_ips", 84 | "wan_proxy_ipv4_ips", "wan_proxy_ipv6_ips", 85 | "wan_direct_ipv4_ips", "wan_direct_ipv6_ips" 86 | ]; 87 | const control_info = {}; 88 | 89 | for (let i in control_options) 90 | control_info[i] = uci.get(cfgname, 'control', i); 91 | /* UCI config end */ 92 | -%} 93 | 94 | {# Reserved addresses -#} 95 | set homeproxy_local_addr_v4 { 96 | type ipv4_addr 97 | flags interval 98 | auto-merge 99 | elements = { 100 | 0.0.0.0/8, 101 | 10.0.0.0/8, 102 | 100.64.0.0/10, 103 | 127.0.0.0/8, 104 | 169.254.0.0/16, 105 | 172.16.0.0/12, 106 | 192.0.0.0/24, 107 | 192.0.2.0/24, 108 | 192.31.196.0/24, 109 | 192.52.193.0/24, 110 | 192.88.99.0/24, 111 | 192.168.0.0/16, 112 | 192.175.48.0/24, 113 | 198.18.0.0/15, 114 | 198.51.100.0/24, 115 | 203.0.113.0/24, 116 | 224.0.0.0/4, 117 | 240.0.0.0/4 118 | } 119 | } 120 | {% if (ipv6_support === '1'): %} 121 | set homeproxy_local_addr_v6 { 122 | type ipv6_addr 123 | flags interval 124 | auto-merge 125 | elements = { 126 | ::/128, 127 | ::1/128, 128 | ::ffff:0:0/96, 129 | 100::/64, 130 | 64:ff9b::/96, 131 | 2001::/32, 132 | 2001:10::/28, 133 | 2001:20::/28, 134 | 2001:db8::/28, 135 | 2002::/16, 136 | fc00::/7, 137 | fe80::/10, 138 | ff00::/8 139 | } 140 | } 141 | {% endif %} 142 | 143 | {% if (routing_mode === 'gfwlist'): %} 144 | set homeproxy_gfw_list_v4 { 145 | type ipv4_addr 146 | flags interval 147 | auto-merge 148 | } 149 | {% if (ipv6_support === '1'): %} 150 | set homeproxy_gfw_list_v6 { 151 | type ipv6_addr 152 | flags interval 153 | auto-merge 154 | } 155 | {% endif /* ipv6_support */ %} 156 | {% elif (match(routing_mode, /mainland_china/) || bypass_cn_traffic === '1'): %} 157 | set homeproxy_mainland_addr_v4 { 158 | type ipv4_addr 159 | flags interval 160 | auto-merge 161 | elements = { 162 | {% for (let cnip4 in split(trim(readfile(resources_dir + '/china_ip4.txt')), /[\r\n]/)): %} 163 | {{ cnip4 }}, 164 | {% endfor %} 165 | } 166 | } 167 | {% if ((ipv6_support === '1') || china_dns_server): %} 168 | set homeproxy_mainland_addr_v6 { 169 | type ipv6_addr 170 | flags interval 171 | auto-merge 172 | elements = { 173 | {% for (let cnip6 in split(trim(readfile(resources_dir + '/china_ip6.txt')), /[\r\n]/)): %} 174 | {{ cnip6 }}, 175 | {% endfor %} 176 | } 177 | } 178 | {% endif /* ipv6_support */ %} 179 | {% endif /* routing_mode */ %} 180 | 181 | {# WAN ACL addresses #} 182 | set homeproxy_wan_proxy_addr_v4 { 183 | type ipv4_addr 184 | flags interval 185 | auto-merge 186 | {% if (control_info.wan_proxy_ipv4_ips): %} 187 | elements = { {{ join(', ', control_info.wan_proxy_ipv4_ips) }} } 188 | {% endif %} 189 | } 190 | 191 | {% if (ipv6_support === '1'): %} 192 | set homeproxy_wan_proxy_addr_v6 { 193 | type ipv6_addr 194 | flags interval 195 | auto-merge 196 | {% if (control_info.wan_proxy_ipv6_ips): %} 197 | elements = { {{ join(', ', control_info.wan_proxy_ipv6_ips) }} } 198 | {% endif /* wan_proxy_ipv6_ips*/ %} 199 | } 200 | {% endif /* ipv6_support */ %} 201 | 202 | set homeproxy_wan_direct_addr_v4 { 203 | type ipv4_addr 204 | flags interval 205 | auto-merge 206 | {% if (control_info.wan_direct_ipv4_ips): %} 207 | elements = { {{ join(', ', control_info.wan_direct_ipv4_ips) }} } 208 | {% endif %} 209 | } 210 | 211 | {% if (ipv6_support === '1'): %} 212 | set homeproxy_wan_direct_addr_v6 { 213 | type ipv6_addr 214 | flags interval 215 | auto-merge 216 | {% if (control_info.wan_direct_ipv6_ips): %} 217 | elements = { {{ join(', ', control_info.wan_direct_ipv6_ips) }} } 218 | {% endif /* wan_direct_ipv6_ips */ %} 219 | } 220 | {% endif /* ipv6_support */ %} 221 | 222 | {% if (routing_port !== 'all'): %} 223 | set homeproxy_routing_port { 224 | type inet_service 225 | flags interval 226 | auto-merge 227 | elements = { {{ join(', ', split(routing_port, ',')) }} } 228 | } 229 | {% endif %} 230 | 231 | {# TCP redirect #} 232 | {% if (match(proxy_mode, /redirect/)): %} 233 | chain homeproxy_redirect_proxy { 234 | meta l4proto tcp counter redirect to :{{ redirect_port }} 235 | } 236 | 237 | chain homeproxy_redirect_proxy_port { 238 | {% if (routing_port !== 'all'): %} 239 | tcp dport != @homeproxy_routing_port counter return 240 | {% endif %} 241 | goto homeproxy_redirect_proxy 242 | } 243 | 244 | chain homeproxy_redirect_lanac { 245 | {% if (control_info.listen_interfaces): %} 246 | meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return 247 | {% endif %} 248 | meta mark {{ self_mark }} counter return 249 | 250 | {% if (control_info.lan_proxy_mode === 'listed_only'): %} 251 | {% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %} 252 | ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_redirect 253 | {% endif /* lan_proxy_ipv4_ips */ %} 254 | {% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %} 255 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect 256 | {% endfor /* lan_proxy_ipv6_ips */ %} 257 | {% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %} 258 | ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_redirect 259 | {% endif /* lan_proxy_mac_addrs */ %} 260 | {% elif (control_info.lan_proxy_mode === 'except_listed'): %} 261 | {% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %} 262 | ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return 263 | {% endif /* lan_direct_ipv4_ips */ %} 264 | {% for (let ipv6 in control_info.lan_direct_ipv6_ips): %} 265 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter return 266 | {% endfor /* lan_direct_ipv6_ips */ %} 267 | {% if (!isEmpty(control_info.lan_direct_mac_addrs)): %} 268 | ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return 269 | {% endif /* lan_direct_mac_addrs */ %} 270 | {% endif /* lan_proxy_mode */ %} 271 | 272 | {% if (control_info.lan_proxy_mode !== 'listed_only'): %} 273 | counter goto homeproxy_redirect 274 | {% endif %} 275 | } 276 | 277 | chain homeproxy_redirect { 278 | meta mark {{ self_mark }} counter return 279 | 280 | ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_redirect_proxy_port 281 | {% if (ipv6_support === '1'): %} 282 | ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_redirect_proxy_port 283 | {% endif %} 284 | 285 | ip daddr @homeproxy_local_addr_v4 counter return 286 | {% if (ipv6_support === '1'): %} 287 | ip6 daddr @homeproxy_local_addr_v6 counter return 288 | {% endif %} 289 | 290 | {% if (routing_mode !== 'custom'): %} 291 | {% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %} 292 | ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_redirect_proxy_port 293 | {% endif /* lan_global_proxy_ipv4_ips */ %} 294 | {% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %} 295 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy_port 296 | {% endfor /* lan_global_proxy_ipv6_ips */ %} 297 | {% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %} 298 | ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_redirect_proxy_port 299 | {% endif /* lan_global_proxy_mac_addrs */ %} 300 | {% endif /* routing_mode */ %} 301 | 302 | ip daddr @homeproxy_wan_direct_addr_v4 counter return 303 | {% if (ipv6_support === '1'): %} 304 | ip6 daddr @homeproxy_wan_direct_addr_v6 counter return 305 | {% endif /* ipv6_support */ %} 306 | 307 | {% if (routing_mode === 'gfwlist'): %} 308 | ip daddr != @homeproxy_gfw_list_v4 counter return 309 | {% if (ipv6_support === '1'): %} 310 | ip6 daddr != @homeproxy_gfw_list_v6 counter return 311 | {% endif /* ipv6_support */ %} 312 | {% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %} 313 | ip daddr @homeproxy_mainland_addr_v4 counter return 314 | {% if (ipv6_support === '1'): %} 315 | ip6 daddr @homeproxy_mainland_addr_v6 counter return 316 | {% endif /* ipv6_support */ %} 317 | {% elif (routing_mode === 'proxy_mainland_china'): %} 318 | ip daddr != @homeproxy_mainland_addr_v4 counter return 319 | {% if (ipv6_support === '1'): %} 320 | ip6 daddr != @homeproxy_mainland_addr_v6 counter return 321 | {% endif /* ipv6_support */ %} 322 | {% endif /* routing_mode */ %} 323 | 324 | {% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %} 325 | ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_redirect_proxy 326 | {% endif /* lan_gaming_mode_ipv4_ips */ %} 327 | {% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %} 328 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_redirect_proxy 329 | {% endfor /* lan_gaming_mode_ipv6_ips */ %} 330 | {% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %} 331 | ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_redirect_proxy 332 | {% endif /* lan_gaming_mode_mac_addrs */ %} 333 | 334 | counter goto homeproxy_redirect_proxy_port 335 | } 336 | 337 | chain homeproxy_output_redir { 338 | type nat hook output priority filter -105; policy accept 339 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect 340 | } 341 | 342 | chain dstnat { 343 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac 344 | } 345 | {% endif %} 346 | 347 | {# UDP tproxy #} 348 | {% if (match(proxy_mode, /tproxy/) && (outbound_udp_node !== 'nil' || routing_mode === 'custom')): %} 349 | chain homeproxy_mangle_tproxy { 350 | meta l4proto udp mark set {{ tproxy_mark }} tproxy ip to 127.0.0.1:{{ tproxy_port }} counter accept 351 | {% if (ipv6_support === '1'): %} 352 | meta l4proto udp mark set {{ tproxy_mark }} tproxy ip6 to [::1]:{{ tproxy_port }} counter accept 353 | {% endif %} 354 | } 355 | 356 | chain homeproxy_mangle_tproxy_port { 357 | {% if (routing_port !== 'all'): %} 358 | udp dport != @homeproxy_routing_port counter return 359 | {% endif %} 360 | goto homeproxy_mangle_tproxy 361 | } 362 | 363 | chain homeproxy_mangle_mark { 364 | {% if (routing_port !== 'all'): %} 365 | udp dport != @homeproxy_routing_port counter return 366 | {% endif %} 367 | meta l4proto udp mark set {{ tproxy_mark }} counter accept 368 | } 369 | 370 | chain homeproxy_mangle_lanac { 371 | {% if (control_info.listen_interfaces): %} 372 | meta iifname != {{ array_to_nftarr(split(join(' ', control_info.listen_interfaces) + ' lo', ' ')) }} counter return 373 | {% endif %} 374 | meta mark {{ self_mark }} counter return 375 | 376 | {% if (control_info.lan_proxy_mode === 'listed_only'): %} 377 | {% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %} 378 | ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_prerouting 379 | {% endif /* lan_proxy_ipv4_ips */ %} 380 | {% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %} 381 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_prerouting 382 | {% endfor /* lan_proxy_ipv6_ips */ %} 383 | {% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %} 384 | ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_prerouting 385 | {% endif /* lan_proxy_mac_addrs */ %} 386 | {% elif (control_info.lan_proxy_mode === 'except_listed'): %} 387 | {% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %} 388 | ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return 389 | {% endif /* lan_direct_ipv4_ips */ %} 390 | {% for (let ipv6 in control_info.lan_direct_ipv6_ips): %} 391 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter return 392 | {% endfor /* lan_direct_ipv6_ips */ %} 393 | {% if (!isEmpty(control_info.lan_direct_mac_addrs)): %} 394 | ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return 395 | {% endif /* lan_direct_mac_addrs */ %} 396 | {% endif /* lan_proxy_mode */ %} 397 | 398 | {% if (control_info.lan_proxy_mode !== 'listed_only'): %} 399 | counter goto homeproxy_mangle_prerouting 400 | {% endif %} 401 | } 402 | 403 | chain homeproxy_mangle_prerouting { 404 | ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tproxy_port 405 | {% if (ipv6_support === '1'): %} 406 | ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tproxy_port 407 | {% endif %} 408 | 409 | ip daddr @homeproxy_local_addr_v4 counter return 410 | {% if (ipv6_support === '1'): %} 411 | ip6 daddr @homeproxy_local_addr_v6 counter return 412 | {% endif %} 413 | 414 | {% if (routing_mode !== 'custom'): %} 415 | {% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %} 416 | ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tproxy_port 417 | {% endif /* lan_global_proxy_ipv4_ips */ %} 418 | {% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %} 419 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy_port 420 | {% endfor /* lan_global_proxy_ipv6_ips */ %} 421 | {% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %} 422 | ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tproxy_port 423 | {% endif /* lan_global_proxy_mac_addrs */ %} 424 | {% endif /* routing_mode */ %} 425 | 426 | ip daddr @homeproxy_wan_direct_addr_v4 counter return 427 | {% if (ipv6_support === '1'): %} 428 | ip6 daddr @homeproxy_wan_direct_addr_v6 counter return 429 | {% endif /* ipv6_support */ %} 430 | 431 | {% if (routing_mode === 'gfwlist'): %} 432 | ip daddr != @homeproxy_gfw_list_v4 counter return 433 | {% if (ipv6_support === '1'): %} 434 | ip6 daddr != @homeproxy_gfw_list_v6 counter return 435 | {% endif /* ipv6_support */ %} 436 | udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC" 437 | {% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %} 438 | ip daddr @homeproxy_mainland_addr_v4 counter return 439 | {% if (ipv6_support === '1'): %} 440 | ip6 daddr @homeproxy_mainland_addr_v6 counter return 441 | {% endif /* ipv6_support */ %} 442 | {% if (routing_mode !== 'custom'): %} 443 | udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC" 444 | {% endif /* routing_mode */ %} 445 | {% elif (routing_mode === 'proxy_mainland_china'): %} 446 | ip daddr != @homeproxy_mainland_addr_v4 counter return 447 | {% if (ipv6_support === '1'): %} 448 | ip6 daddr != @homeproxy_mainland_addr_v6 counter return 449 | {% endif /* ipv6_support */ %} 450 | {% endif /* routing_mode */ %} 451 | 452 | {% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %} 453 | ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter goto homeproxy_mangle_tproxy 454 | {% endif /* lan_gaming_mode_ipv4_ips */ %} 455 | {% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %} 456 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tproxy 457 | {% endfor /* lan_gaming_mode_ipv6_ips */ %} 458 | {% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %} 459 | ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter goto homeproxy_mangle_tproxy 460 | {% endif /* lan_gaming_mode_mac_addrs */ %} 461 | 462 | counter goto homeproxy_mangle_tproxy_port 463 | } 464 | 465 | chain homeproxy_mangle_output { 466 | meta mark {{ self_mark }} counter return 467 | 468 | ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_mark 469 | {% if (ipv6_support === '1'): %} 470 | ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_mark 471 | {% endif %} 472 | 473 | ip daddr @homeproxy_local_addr_v4 counter return 474 | {% if (ipv6_support === '1'): %} 475 | ip6 daddr @homeproxy_local_addr_v6 counter return 476 | {% endif %} 477 | 478 | ip daddr @homeproxy_wan_direct_addr_v4 counter return 479 | {% if (ipv6_support === '1'): %} 480 | ip6 daddr @homeproxy_wan_direct_addr_v6 counter return 481 | {% endif /* ipv6_support */ %} 482 | 483 | {% if (routing_mode === 'gfwlist'): %} 484 | ip daddr != @homeproxy_gfw_list_v4 counter return 485 | {% if (ipv6_support === '1'): %} 486 | ip6 daddr != @homeproxy_gfw_list_v6 counter return 487 | {% endif /* ipv6_support */ %} 488 | {% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %} 489 | ip daddr @homeproxy_mainland_addr_v4 counter return 490 | {% if (ipv6_support === '1'): %} 491 | ip6 daddr @homeproxy_mainland_addr_v6 counter return 492 | {% endif /* ipv6_support */ %} 493 | {% elif (routing_mode === 'proxy_mainland_china'): %} 494 | ip daddr != @homeproxy_mainland_addr_v4 counter return 495 | {% if (ipv6_support === '1'): %} 496 | ip6 daddr != @homeproxy_mainland_addr_v6 counter return 497 | {% endif /* ipv6_support */ %} 498 | {% endif /* routing_mode */ %} 499 | 500 | counter goto homeproxy_mangle_mark 501 | } 502 | 503 | chain mangle_prerouting { 504 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_lanac 505 | } 506 | 507 | chain mangle_output { 508 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto udp jump homeproxy_mangle_output 509 | } 510 | {% endif %} 511 | 512 | {# TUN #} 513 | {% if (match(proxy_mode, /tun/)): %} 514 | chain homeproxy_mangle_lanac { 515 | iifname {{ tun_name }} counter return 516 | 517 | {% if (control_info.listen_interfaces): %} 518 | meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return 519 | {% endif %} 520 | 521 | {% if (control_info.lan_proxy_mode === 'listed_only'): %} 522 | {% if (!isEmpty(control_info.lan_proxy_ipv4_ips)): %} 523 | ip saddr {{ array_to_nftarr(control_info.lan_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun 524 | {% endif /* lan_proxy_ipv4_ips */ %} 525 | {% for (let ipv6 in control_info.lan_proxy_ipv6_ips): %} 526 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun 527 | {% endfor /* lan_proxy_ipv6_ips */ %} 528 | {% if (!isEmpty(control_info.lan_proxy_mac_addrs)): %} 529 | ether saddr {{ array_to_nftarr(control_info.lan_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun 530 | {% endif /* lan_proxy_mac_addrs */ %} 531 | {% elif (control_info.lan_proxy_mode === 'except_listed'): %} 532 | {% if (!isEmpty(control_info.lan_direct_ipv4_ips)): %} 533 | ip saddr {{ array_to_nftarr(control_info.lan_direct_ipv4_ips) }} counter return 534 | {% endif /* lan_direct_ipv4_ips */ %} 535 | {% for (let ipv6 in control_info.lan_direct_ipv6_ips): %} 536 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter return 537 | {% endfor /* lan_direct_ipv6_ips */ %} 538 | {% if (!isEmpty(control_info.lan_direct_mac_addrs)): %} 539 | ether saddr {{ array_to_nftarr(control_info.lan_direct_mac_addrs) }} counter return 540 | {% endif /* lan_direct_mac_addrs */ %} 541 | {% endif /* lan_proxy_mode */ %} 542 | 543 | {% if (control_info.lan_proxy_mode !== 'listed_only'): %} 544 | counter goto homeproxy_mangle_tun 545 | {% endif %} 546 | } 547 | 548 | chain homeproxy_mangle_tun_mark { 549 | {% if (routing_port !== 'all'): %} 550 | {% if (proxy_mode === 'tun'): %} 551 | tcp dport != @homeproxy_routing_port counter return 552 | {% endif /* proxy_mode */ %} 553 | udp dport != @homeproxy_routing_port counter return 554 | {% endif /* routing_port */ %} 555 | 556 | counter mark set {{ tun_mark }} 557 | } 558 | 559 | chain homeproxy_mangle_tun { 560 | iifname {{ tun_name }} counter return 561 | 562 | ip daddr @homeproxy_wan_proxy_addr_v4 counter goto homeproxy_mangle_tun_mark 563 | {% if (ipv6_support === '1'): %} 564 | ip6 daddr @homeproxy_wan_proxy_addr_v6 counter goto homeproxy_mangle_tun_mark 565 | {% endif %} 566 | 567 | ip daddr @homeproxy_local_addr_v4 counter return 568 | {% if (ipv6_support === '1'): %} 569 | ip6 daddr @homeproxy_local_addr_v6 counter return 570 | {% endif %} 571 | 572 | {% if (routing_mode !== 'custom'): %} 573 | {% if (!isEmpty(control_info.lan_global_proxy_ipv4_ips)): %} 574 | ip saddr {{ array_to_nftarr(control_info.lan_global_proxy_ipv4_ips) }} counter goto homeproxy_mangle_tun_mark 575 | {% endif /* lan_global_proxy_ipv4_ips */ %} 576 | {% for (let ipv6 in control_info.lan_global_proxy_ipv6_ips): %} 577 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter goto homeproxy_mangle_tun_mark 578 | {% endfor /* lan_global_proxy_ipv6_ips */ %} 579 | {% if (!isEmpty(control_info.lan_global_proxy_mac_addrs)): %} 580 | ether saddr {{ array_to_nftarr(control_info.lan_global_proxy_mac_addrs) }} counter goto homeproxy_mangle_tun_mark 581 | {% endif /* lan_global_proxy_mac_addrs */ %} 582 | {% endif /* routing_mode */ %} 583 | 584 | {% if (control_info.wan_direct_ipv4_ips): %} 585 | ip daddr {{ array_to_nftarr(control_info.wan_direct_ipv4_ips) }} counter return 586 | {% endif /* wan_direct_ipv4_ips */ %} 587 | {% if (control_info.wan_direct_ipv6_ips): %} 588 | ip6 daddr {{ array_to_nftarr(control_info.wan_direct_ipv6_ips) }} counter return 589 | {% endif /* wan_direct_ipv6_ips */ %} 590 | 591 | {% if (routing_mode === 'gfwlist'): %} 592 | ip daddr != @homeproxy_gfw_list_v4 counter return 593 | {% if (ipv6_support === '1'): %} 594 | ip6 daddr != @homeproxy_gfw_list_v6 counter return 595 | {% endif /* ipv6_support */ %} 596 | udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC" 597 | {% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %} 598 | ip daddr @homeproxy_mainland_addr_v4 counter return 599 | {% if (ipv6_support === '1'): %} 600 | ip6 daddr @homeproxy_mainland_addr_v6 counter return 601 | {% endif /* ipv6_support */ %} 602 | {% if (routing_mode !== 'custom'): %} 603 | udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Fuck you QUIC" 604 | {% endif /* routing_mode */ %} 605 | {% elif (routing_mode === 'proxy_mainland_china'): %} 606 | ip daddr != @homeproxy_mainland_addr_v4 counter return 607 | {% if (ipv6_support === '1'): %} 608 | ip6 daddr != @homeproxy_mainland_addr_v6 counter return 609 | {% endif /* ipv6_support */ %} 610 | {% endif /* routing_mode */ %} 611 | 612 | {% if (!isEmpty(control_info.lan_gaming_mode_ipv4_ips)): %} 613 | ip saddr {{ array_to_nftarr(control_info.lan_gaming_mode_ipv4_ips) }} counter mark set {{ tun_mark }} 614 | {% endif /* lan_gaming_mode_ipv4_ips */ %} 615 | {% for (let ipv6 in control_info.lan_gaming_mode_ipv6_ips): %} 616 | ip6 saddr {{ resolve_ipv6(ipv6) }} counter mark set {{ tun_mark }} 617 | {% endfor /* lan_gaming_mode_ipv6_ips */ %} 618 | {% if (!isEmpty(control_info.lan_gaming_mode_mac_addrs)): %} 619 | ether saddr {{ array_to_nftarr(control_info.lan_gaming_mode_mac_addrs) }} counter mark set {{ tun_mark }} 620 | {% endif /* lan_gaming_mode_mac_addrs */ %} 621 | 622 | counter goto homeproxy_mangle_tun_mark 623 | } 624 | 625 | chain mangle_prerouting { 626 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_lanac 627 | } 628 | 629 | chain mangle_output { 630 | meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto { {{ (proxy_mode === 'tun') ? 'tcp, udp' : 'udp' }} } jump homeproxy_mangle_tun 631 | } 632 | {% endif %} 633 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/firewall_pre.ut: -------------------------------------------------------------------------------- 1 | #!/usr/bin/utpl -S 2 | 3 | {%- 4 | import { cursor } from 'uci'; 5 | 6 | const cfgname = 'homeproxy'; 7 | const uci = cursor(); 8 | uci.load(cfgname); 9 | 10 | const routing_mode = uci.get(cfgname, 'config', 'routing_mode') || 'bypass_mainland_china', 11 | proxy_mode = uci.get(cfgname, 'config', 'proxy_mode') || 'redirect_tproxy'; 12 | 13 | let outbound_node, tun_name; 14 | if (match(proxy_mode, /tun/)) { 15 | if (routing_mode === 'custom') 16 | outbound_node = uci.get(cfgname, 'routing', 'default_outbound') || 'nil'; 17 | else 18 | outbound_node = uci.get(cfgname, 'config', 'main_node') || 'nil'; 19 | 20 | if (outbound_node !== 'nil') 21 | tun_name = uci.get(cfgname, 'infra', 'tun_name') || 'singtun0'; 22 | } 23 | 24 | const server_enabled = uci.get(cfgname, 'server', 'enabled'); 25 | let auto_firewall = '0'; 26 | if (server_enabled === '1') 27 | auto_firewall = uci.get(cfgname, 'server', 'auto_firewall') || '0'; 28 | 29 | -%} 30 | 31 | {% if (tun_name): %} 32 | chain forward { 33 | oifname {{ tun_name }} counter accept comment "!{{ cfgname }}: accept tun forward" 34 | } 35 | {% endif %} 36 | 37 | {% if (tun_name || auto_firewall === '1'): %} 38 | chain input { 39 | {% if (tun_name): %} 40 | iifname {{ tun_name }} counter accept comment "!{{ cfgname }}: accept tun input" 41 | {% endif %} 42 | {% 43 | uci.foreach(cfgname, 'server', (s) => { 44 | if (s.enabled !== '1') 45 | return; 46 | 47 | let proto = s.network || '{ tcp, udp }'; 48 | printf(' meta l4proto %s th dport %s counter accept comment "!%s: accept server %s"\n', 49 | proto, s.port, cfgname, s['.name']); 50 | }); 51 | %} 52 | } 53 | {% endif %} 54 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/generate_client.uc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ucode 2 | /* 3 | * SPDX-License-Identifier: GPL-2.0-only 4 | * 5 | * Copyright (C) 2023 ImmortalWrt.org 6 | */ 7 | 8 | 'use strict'; 9 | 10 | import { readfile, writefile } from 'fs'; 11 | import { isnan } from 'math'; 12 | import { cursor } from 'uci'; 13 | 14 | import { 15 | executeCommand, isEmpty, strToBool, strToInt, 16 | removeBlankAttrs, validateHostname, validation, 17 | HP_DIR, RUN_DIR 18 | } from 'homeproxy'; 19 | 20 | /* UCI config start */ 21 | const uci = cursor(); 22 | 23 | const uciconfig = 'homeproxy'; 24 | uci.load(uciconfig); 25 | 26 | const uciinfra = 'infra', 27 | ucimain = 'config', 28 | uciexp = 'experimental', 29 | ucicontrol = 'control'; 30 | 31 | const ucidnssetting = 'dns', 32 | ucidnsserver = 'dns_server', 33 | ucidnsrule = 'dns_rule'; 34 | 35 | const uciroutingsetting = 'routing', 36 | uciroutingnode = 'routing_node', 37 | uciroutingrule = 'routing_rule'; 38 | 39 | const ucinode = 'node'; 40 | const uciruleset = 'ruleset'; 41 | 42 | const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainland_china'; 43 | 44 | let wan_dns = executeCommand('ifstatus wan | jsonfilter -e \'@["dns-server"][0]\''); 45 | if (wan_dns.exitcode === 0 && trim(wan_dns.stdout)) 46 | wan_dns = trim(wan_dns.stdout); 47 | else 48 | wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '208.67.222.222' : '114.114.114.114'; 49 | 50 | const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333'; 51 | 52 | let main_node, main_udp_node, dedicated_udp_node, default_outbound, sniff_override = '1', 53 | dns_server, dns_default_strategy, dns_default_server, dns_disable_cache, dns_disable_cache_expire, 54 | dns_independent_cache, dns_client_subnet, direct_domain_list; 55 | 56 | if (routing_mode !== 'custom') { 57 | main_node = uci.get(uciconfig, ucimain, 'main_node') || 'nil'; 58 | main_udp_node = uci.get(uciconfig, ucimain, 'main_udp_node') || 'nil'; 59 | dedicated_udp_node = !isEmpty(main_udp_node) && !(main_udp_node in ['same', main_node]); 60 | 61 | dns_server = uci.get(uciconfig, ucimain, 'dns_server'); 62 | if (isEmpty(dns_server) || dns_server === 'wan') 63 | dns_server = wan_dns; 64 | 65 | direct_domain_list = trim(readfile(HP_DIR + '/resources/direct_list.txt')); 66 | if (direct_domain_list) 67 | direct_domain_list = split(direct_domain_list, /[\r\n]/); 68 | } else { 69 | /* DNS settings */ 70 | dns_default_strategy = uci.get(uciconfig, ucidnssetting, 'default_strategy'); 71 | dns_default_server = uci.get(uciconfig, ucidnssetting, 'default_server'); 72 | dns_disable_cache = uci.get(uciconfig, ucidnssetting, 'disable_cache'); 73 | dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire'); 74 | dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache'); 75 | dns_client_subnet = uci.get(uciconfig, ucidnssetting, 'client_subnet'); 76 | 77 | /* Routing settings */ 78 | default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil'; 79 | sniff_override = uci.get(uciconfig, uciroutingsetting, 'sniff_override'); 80 | } 81 | 82 | const proxy_mode = uci.get(uciconfig, ucimain, 'proxy_mode') || 'redirect_tproxy', 83 | ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0', 84 | default_interface = uci.get(uciconfig, ucicontrol, 'bind_interface'); 85 | 86 | const cache_file_store_rdrc = uci.get(uciconfig, uciexp, 'cache_file_store_rdrc'), 87 | cache_file_rdrc_timeout = uci.get(uciconfig, uciexp, 'cache_file_rdrc_timeout'); 88 | 89 | const enable_clash_api = uci.get(uciconfig, uciexp, 'enable_clash_api'), 90 | external_ui = uci.get(uciconfig, uciexp, 'external_ui'), 91 | external_ui_download_url = uci.get(uciconfig, uciexp, 'external_ui_download_url'), 92 | external_ui_download_detour = uci.get(uciconfig, uciexp, 'external_ui_download_detour'), 93 | secret = uci.get(uciconfig, uciexp, 'secret'), 94 | default_mode = uci.get(uciconfig, uciexp, 'default_mode'), 95 | external_controller = uci.get(uciconfig, uciexp, 'external_controller'); 96 | 97 | const mixed_port = uci.get(uciconfig, uciinfra, 'mixed_port') || '5330'; 98 | let self_mark, redirect_port, tproxy_port, 99 | tun_name, tun_addr4, tun_addr6, tun_mtu, tun_gso, 100 | tcpip_stack, endpoint_independent_nat; 101 | if (match(proxy_mode, /redirect/)) { 102 | self_mark = uci.get(uciconfig, 'infra', 'self_mark') || '100'; 103 | redirect_port = uci.get(uciconfig, 'infra', 'redirect_port') || '5331'; 104 | } 105 | if (match(proxy_mode), /tproxy/) 106 | if (main_udp_node !== 'nil' || routing_mode === 'custom') 107 | tproxy_port = uci.get(uciconfig, 'infra', 'tproxy_port') || '5332'; 108 | if (match(proxy_mode), /tun/) { 109 | tun_name = uci.get(uciconfig, uciinfra, 'tun_name') || 'singtun0'; 110 | tun_addr4 = uci.get(uciconfig, uciinfra, 'tun_addr4') || '172.19.0.1/30'; 111 | tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126'; 112 | tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000'; 113 | tun_gso = '0'; 114 | tcpip_stack = 'system'; 115 | if (routing_mode === 'custom') { 116 | tun_gso = uci.get(uciconfig, uciroutingsetting, 'tun_gso') || '0'; 117 | tcpip_stack = uci.get(uciconfig, uciroutingsetting, 'tcpip_stack') || 'system'; 118 | endpoint_independent_nat = uci.get(uciconfig, uciroutingsetting, 'endpoint_independent_nat'); 119 | } 120 | } 121 | /* UCI config end */ 122 | 123 | /* Config helper start */ 124 | function parse_port(strport) { 125 | if (type(strport) !== 'array' || isEmpty(strport)) 126 | return null; 127 | 128 | let ports = []; 129 | for (let i in strport) 130 | push(ports, int(i)); 131 | 132 | return ports; 133 | 134 | } 135 | 136 | function parse_dnsquery(strquery) { 137 | if (type(strquery) !== 'array' || isEmpty(strquery)) 138 | return null; 139 | 140 | let querys = []; 141 | for (let i in strquery) 142 | isnan(int(i)) ? push(querys, i) : push(querys, int(i)); 143 | 144 | return querys; 145 | 146 | } 147 | 148 | function generate_outbound(node) { 149 | if (type(node) !== 'object' || isEmpty(node)) 150 | return null; 151 | 152 | const outbound = { 153 | type: node.type, 154 | tag: node.label, 155 | routing_mark: (node.type !== 'urltest' && node.type !== 'selector') ? strToInt(self_mark) : null, 156 | 157 | server: node.address, 158 | server_port: strToInt(node.port), 159 | 160 | username: (node.type !== 'ssh') ? node.username : null, 161 | user: (node.type === 'ssh') ? node.username : null, 162 | password: node.password, 163 | 164 | /* urltest */ 165 | outbounds: node.outbounds, 166 | url: node.url, 167 | interval: node.interval, 168 | tolerance: strToInt(node.tolerance), 169 | idle_timeout: node.idle_timeout, 170 | default: node.default, 171 | interrupt_exist_connections: (node.interrupt_exist_connections === '1') || null, 172 | 173 | /* Direct */ 174 | override_address: node.override_address, 175 | override_port: strToInt(node.override_port), 176 | /* Hysteria (2) */ 177 | up_mbps: strToInt(node.hysteria_up_mbps), 178 | down_mbps: strToInt(node.hysteria_down_mbps), 179 | obfs: node.hysteria_obfs_type ? { 180 | type: node.hysteria_obfs_type, 181 | password: node.hysteria_obfs_password 182 | } : node.hysteria_obfs_password, 183 | auth: (node.hysteria_auth_type === 'base64') ? node.hysteria_auth_payload : null, 184 | auth_str: (node.hysteria_auth_type === 'string') ? node.hysteria_auth_payload : null, 185 | recv_window_conn: strToInt(node.hysteria_recv_window_conn), 186 | recv_window: strToInt(node.hysteria_revc_window), 187 | disable_mtu_discovery: strToBool(node.hysteria_disable_mtu_discovery), 188 | /* Shadowsocks */ 189 | method: node.shadowsocks_encrypt_method, 190 | plugin: node.shadowsocks_plugin, 191 | plugin_opts: node.shadowsocks_plugin_opts, 192 | /* ShadowTLS / Socks */ 193 | version: (node.type === 'shadowtls') ? strToInt(node.shadowtls_version) : ((node.type === 'socks') ? node.socks_version : null), 194 | /* SSH */ 195 | client_version: node.ssh_client_version, 196 | host_key: node.ssh_host_key, 197 | host_key_algorithms: node.ssh_host_key_algo, 198 | private_key: node.ssh_priv_key, 199 | private_key_passphrase: node.ssh_priv_key_pp, 200 | /* Tuic */ 201 | uuid: node.uuid, 202 | congestion_control: node.tuic_congestion_control, 203 | udp_relay_mode: node.tuic_udp_relay_mode, 204 | udp_over_stream: strToBool(node.tuic_udp_over_stream), 205 | zero_rtt_handshake: strToBool(node.tuic_enable_zero_rtt), 206 | heartbeat: node.tuic_heartbeat ? (node.tuic_heartbeat + 's') : null, 207 | /* VLESS / VMess */ 208 | flow: node.vless_flow, 209 | alter_id: strToInt(node.vmess_alterid), 210 | security: node.vmess_encrypt, 211 | global_padding: node.vmess_global_padding ? (node.vmess_global_padding === '1') : null, 212 | authenticated_length: node.vmess_authenticated_length ? (node.vmess_authenticated_length === '1') : null, 213 | packet_encoding: node.packet_encoding, 214 | /* WireGuard */ 215 | system_interface: (node.type === 'wireguard') || null, 216 | gso: (node.wireguard_gso === '1') || null, 217 | interface_name: (node.type === 'wireguard') ? 'wg-' + node['.name'] + '-out' : null, 218 | local_address: node.wireguard_local_address, 219 | private_key: node.wireguard_private_key, 220 | peer_public_key: node.wireguard_peer_public_key, 221 | pre_shared_key: node.wireguard_pre_shared_key, 222 | reserved: parse_port(node.wireguard_reserved), 223 | mtu: strToInt(node.wireguard_mtu), 224 | 225 | multiplex: (node.multiplex === '1') ? { 226 | enabled: true, 227 | protocol: node.multiplex_protocol, 228 | max_connections: strToInt(node.multiplex_max_connections), 229 | min_streams: strToInt(node.multiplex_min_streams), 230 | max_streams: strToInt(node.multiplex_max_streams), 231 | padding: (node.multiplex_padding === '1'), 232 | brutal: (node.multiplex_brutal === '1') ? { 233 | enabled: true, 234 | up_mbps: strToInt(node.multiplex_brutal_up), 235 | down_mbps: strToInt(node.multiplex_brutal_down) 236 | } : null 237 | } : null, 238 | tls: (node.tls === '1') ? { 239 | enabled: true, 240 | server_name: node.tls_sni, 241 | insecure: (node.tls_insecure === '1'), 242 | alpn: node.tls_alpn, 243 | min_version: node.tls_min_version, 244 | max_version: node.tls_max_version, 245 | cipher_suites: node.tls_cipher_suites, 246 | certificate_path: node.tls_cert_path, 247 | ech: (node.tls_ech === '1') ? { 248 | enabled: true, 249 | dynamic_record_sizing_disabled: (node.tls_ech_tls_disable_drs === '1'), 250 | pq_signature_schemes_enabled: (node.tls_ech_enable_pqss === '1'), 251 | config: node.tls_ech_config 252 | } : null, 253 | utls: !isEmpty(node.tls_utls) ? { 254 | enabled: true, 255 | fingerprint: node.tls_utls 256 | } : null, 257 | reality: (node.tls_reality === '1') ? { 258 | enabled: true, 259 | public_key: node.tls_reality_public_key, 260 | short_id: node.tls_reality_short_id 261 | } : null 262 | } : null, 263 | transport: !isEmpty(node.transport) ? { 264 | type: node.transport, 265 | host: node.http_host || node.httpupgrade_host, 266 | path: node.http_path || node.ws_path, 267 | headers: node.ws_host ? { 268 | Host: node.ws_host 269 | } : null, 270 | method: node.http_method, 271 | max_early_data: strToInt(node.websocket_early_data), 272 | early_data_header_name: node.websocket_early_data_header, 273 | service_name: node.grpc_servicename, 274 | idle_timeout: node.http_idle_timeout ? (node.http_idle_timeout + 's') : null, 275 | ping_timeout: node.http_ping_timeout ? (node.http_ping_timeout + 's') : null, 276 | permit_without_stream: strToBool(node.grpc_permit_without_stream) 277 | } : null, 278 | udp_over_tcp: (node.udp_over_tcp === '1') ? { 279 | enabled: true, 280 | version: strToInt(node.udp_over_tcp_version) 281 | } : null, 282 | tcp_fast_open: strToBool(node.tcp_fast_open), 283 | tcp_multi_path: strToBool(node.tcp_multi_path), 284 | udp_fragment: strToBool(node.udp_fragment) 285 | }; 286 | 287 | return outbound; 288 | } 289 | 290 | function get_outbound(cfg) { 291 | if (isEmpty(cfg)) 292 | return null; 293 | 294 | if (type(cfg) === 'array') { 295 | if ('any-out' in cfg) 296 | return 'any'; 297 | 298 | let outbounds = []; 299 | for (let i in cfg) 300 | push(outbounds, i); 301 | return outbounds; 302 | } else { 303 | if (cfg in ['direct-out', 'block-out']) { 304 | return cfg; 305 | } else { 306 | const node = uci.get(uciconfig, cfg, 'label'); 307 | if (isEmpty(node)) 308 | die(sprintf("%s's node is missing, please check your configuration.", cfg)); 309 | else 310 | return node; 311 | } 312 | } 313 | } 314 | 315 | function get_resolver(cfg) { 316 | if (isEmpty(cfg)) 317 | return null; 318 | 319 | if (cfg in ['default-dns', 'system-dns', 'block-dns']) 320 | return cfg; 321 | else 322 | return cfg; 323 | } 324 | 325 | function get_ruleset(cfg) { 326 | if (isEmpty(cfg)) 327 | return null; 328 | 329 | let rules = []; 330 | for (let i in cfg) 331 | push(rules, isEmpty(i) ? null : i); 332 | return rules; 333 | } 334 | /* Config helper end */ 335 | 336 | const config = {}; 337 | 338 | /* Log */ 339 | config.log = { 340 | disabled: false, 341 | level: 'warn', 342 | output: RUN_DIR + '/sing-box-c.log', 343 | timestamp: true 344 | }; 345 | 346 | /* DNS start */ 347 | /* Default settings */ 348 | config.dns = { 349 | servers: [ 350 | { 351 | tag: 'default-dns', 352 | address: wan_dns, 353 | detour: 'direct-out' 354 | }, 355 | { 356 | tag: 'system-dns', 357 | address: 'local', 358 | detour: 'direct-out' 359 | }, 360 | { 361 | tag: 'block-dns', 362 | address: 'rcode://name_error' 363 | } 364 | ], 365 | rules: [], 366 | strategy: dns_default_strategy, 367 | disable_cache: (dns_disable_cache === '1'), 368 | disable_expire: (dns_disable_cache_expire === '1'), 369 | independent_cache: (dns_independent_cache === '1'), 370 | client_subnet: dns_client_subnet 371 | }; 372 | 373 | if (!isEmpty(main_node)) { 374 | /* Avoid DNS loop */ 375 | const main_node_addr = uci.get(uciconfig, main_node, 'address'); 376 | if (validateHostname(main_node_addr)) 377 | push(config.dns.rules, { 378 | domain: main_node_addr, 379 | server: 'default-dns' 380 | }); 381 | 382 | if (dedicated_udp_node) { 383 | const main_udp_node_addr = uci.get(uciconfig, main_udp_node, 'address'); 384 | if (validateHostname(main_udp_node_addr)) 385 | push(config.dns.rules, { 386 | domain: main_udp_node_addr, 387 | server: 'default-dns' 388 | }); 389 | } 390 | 391 | if (direct_domain_list) 392 | push(config.dns.rules, { 393 | domain_keyword: direct_domain_list, 394 | server: 'default-dns' 395 | }); 396 | 397 | if (isEmpty(config.dns.rules)) 398 | config.dns.rules = null; 399 | 400 | let default_final_dns = 'default-dns'; 401 | /* Main DNS */ 402 | if (dns_server !== wan_dns) { 403 | push(config.dns.servers, { 404 | tag: 'main-dns', 405 | address: 'tcp://' + (validation('ip6addr', dns_server) ? `[${dns_server}]` : dns_server), 406 | strategy: (ipv6_support !== '1') ? 'ipv4_only' : null, 407 | detour: 'main-out' 408 | }); 409 | 410 | default_final_dns = 'main-dns'; 411 | } 412 | 413 | config.dns.final = default_final_dns; 414 | } else if (!isEmpty(default_outbound)) { 415 | /* DNS servers */ 416 | uci.foreach(uciconfig, ucidnsserver, (cfg) => { 417 | if (cfg.enabled !== '1') 418 | return; 419 | 420 | push(config.dns.servers, { 421 | tag: cfg.label, 422 | address: cfg.address, 423 | address: cfg.address, 424 | address_resolver: get_resolver(cfg.address_resolver), 425 | address_strategy: cfg.address_strategy, 426 | strategy: cfg.resolve_strategy, 427 | detour: cfg.outbound, 428 | client_subnet: cfg.client_subnet 429 | }); 430 | }); 431 | 432 | /* DNS rules */ 433 | uci.foreach(uciconfig, ucidnsrule, (cfg) => { 434 | if (cfg.enabled !== '1') 435 | return; 436 | 437 | push(config.dns.rules, { 438 | ip_version: strToInt(cfg.ip_version), 439 | query_type: parse_dnsquery(cfg.query_type), 440 | network: cfg.network, 441 | protocol: cfg.protocol, 442 | domain: cfg.domain, 443 | domain_suffix: cfg.domain_suffix, 444 | domain_keyword: cfg.domain_keyword, 445 | domain_regex: cfg.domain_regex, 446 | port: parse_port(cfg.port), 447 | port_range: cfg.port_range, 448 | source_ip_cidr: cfg.source_ip_cidr, 449 | source_ip_is_private: (cfg.source_ip_is_private === '1') || null, 450 | ip_cidr: cfg.ip_cidr, 451 | ip_is_private: (cfg.ip_is_private === '1') || null, 452 | source_port: parse_port(cfg.source_port), 453 | source_port_range: cfg.source_port_range, 454 | process_name: cfg.process_name, 455 | process_path: cfg.process_path, 456 | user: cfg.user, 457 | rule_set: get_ruleset(cfg.rule_set), 458 | rule_set_ipcidr_match_source: (cfg.rule_set_ipcidr_match_source === '1') || null, 459 | invert: (cfg.invert === '1') || null, 460 | clash_mode: cfg.clash_mode, 461 | outbound: cfg.outbound, 462 | server: get_resolver(cfg.server), 463 | disable_cache: (cfg.dns_disable_cache === '1') || null, 464 | rewrite_ttl: strToInt(cfg.rewrite_ttl), 465 | client_subnet: cfg.client_subnet 466 | }); 467 | }); 468 | 469 | if (isEmpty(config.dns.rules)) 470 | config.dns.rules = null; 471 | 472 | config.dns.final = get_resolver(dns_default_server); 473 | } 474 | /* DNS end */ 475 | 476 | /* Inbound start */ 477 | config.inbounds = []; 478 | 479 | push(config.inbounds, { 480 | type: 'direct', 481 | tag: 'dns-in', 482 | listen: '::', 483 | listen_port: int(dns_port) 484 | }); 485 | 486 | push(config.inbounds, { 487 | type: 'mixed', 488 | tag: 'mixed-in', 489 | listen: '::', 490 | listen_port: int(mixed_port), 491 | sniff: true, 492 | sniff_override_destination: (sniff_override === '1'), 493 | set_system_proxy: false 494 | }); 495 | 496 | if (match(proxy_mode, /redirect/)) 497 | push(config.inbounds, { 498 | type: 'redirect', 499 | tag: 'redirect-in', 500 | 501 | listen: '::', 502 | listen_port: int(redirect_port), 503 | sniff: true, 504 | sniff_override_destination: (sniff_override === '1') 505 | }); 506 | if (match(proxy_mode, /tproxy/)) 507 | push(config.inbounds, { 508 | type: 'tproxy', 509 | tag: 'tproxy-in', 510 | 511 | listen: '::', 512 | listen_port: int(tproxy_port), 513 | network: 'udp', 514 | sniff: true, 515 | sniff_override_destination: (sniff_override === '1') 516 | }); 517 | if (match(proxy_mode, /tun/)) 518 | push(config.inbounds, { 519 | type: 'tun', 520 | tag: 'tun-in', 521 | 522 | interface_name: tun_name, 523 | inet4_address: tun_addr4, 524 | inet6_address: (ipv6_support === '1') ? tun_addr6 : null, 525 | mtu: strToInt(tun_mtu), 526 | gso: (tun_gso === '1'), 527 | auto_route: false, 528 | endpoint_independent_nat: strToBool(endpoint_independent_nat), 529 | stack: tcpip_stack, 530 | sniff: true, 531 | sniff_override_destination: (sniff_override === '1'), 532 | }); 533 | /* Inbound end */ 534 | 535 | /* Outbound start */ 536 | /* Default outbounds */ 537 | config.outbounds = [ 538 | { 539 | type: 'direct', 540 | tag: 'direct-out', 541 | routing_mark: strToInt(self_mark) 542 | }, 543 | { 544 | type: 'block', 545 | tag: 'block-out' 546 | }, 547 | { 548 | type: 'dns', 549 | tag: 'dns-out' 550 | } 551 | ]; 552 | 553 | /* Main outbounds */ 554 | if (!isEmpty(main_node)) { 555 | const main_node_cfg = uci.get_all(uciconfig, main_node) || {}; 556 | push(config.outbounds, generate_outbound(main_node_cfg)); 557 | config.outbounds[length(config.outbounds)-1].tag = 'main-out'; 558 | 559 | if (dedicated_udp_node) { 560 | const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {}; 561 | push(config.outbounds, generate_outbound(main_udp_node_cfg)); 562 | config.outbounds[length(config.outbounds)-1].tag = 'main-udp-out'; 563 | } 564 | } else if (!isEmpty(default_outbound)) 565 | uci.foreach(uciconfig, 'node', (cfg) => { 566 | push(config.outbounds, generate_outbound(cfg)); 567 | config.outbounds[length(config.outbounds)-1].domain_strategy = cfg.domain_strategy; 568 | config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface; 569 | config.outbounds[length(config.outbounds)-1].detour = cfg.outbound; 570 | }); 571 | /* Outbound end */ 572 | 573 | /* Routing rules start */ 574 | /* Default settings */ 575 | config.route = { 576 | rules: [ 577 | { 578 | inbound: 'dns-in', 579 | outbound: 'dns-out' 580 | }, 581 | { 582 | protocol: 'dns', 583 | outbound: 'dns-out' 584 | } 585 | ], 586 | rule_set: [], 587 | auto_detect_interface: isEmpty(default_interface) ? true : null, 588 | default_interface: default_interface 589 | }; 590 | 591 | /* Routing rules */ 592 | if (!isEmpty(main_node)) { 593 | /* Direct list */ 594 | if (length(direct_domain_list)) 595 | push(config.route.rules, { 596 | domain_keyword: direct_domain_list, 597 | outbound: 'direct-out' 598 | }); 599 | 600 | /* Main UDP out */ 601 | if (dedicated_udp_node) 602 | push(config.route.rules, { 603 | network: 'udp', 604 | outbound: 'main-udp-out' 605 | }); 606 | 607 | config.route.final = 'main-out'; 608 | } else if (!isEmpty(default_outbound)) { 609 | uci.foreach(uciconfig, uciroutingrule, (cfg) => { 610 | if (cfg.enabled !== '1') 611 | return null; 612 | 613 | push(config.route.rules, { 614 | ip_version: strToInt(cfg.ip_version), 615 | protocol: cfg.protocol, 616 | network: cfg.network, 617 | domain: cfg.domain, 618 | domain_suffix: cfg.domain_suffix, 619 | domain_keyword: cfg.domain_keyword, 620 | domain_regex: cfg.domain_regex, 621 | source_ip_cidr: cfg.source_ip_cidr, 622 | source_ip_is_private: (cfg.source_ip_is_private === '1') || null, 623 | ip_cidr: cfg.ip_cidr, 624 | ip_is_private: (cfg.ip_is_private === '1') || null, 625 | source_port: parse_port(cfg.source_port), 626 | source_port_range: cfg.source_port_range, 627 | port: parse_port(cfg.port), 628 | port_range: cfg.port_range, 629 | process_name: cfg.process_name, 630 | process_path: cfg.process_path, 631 | user: cfg.user, 632 | rule_set: get_ruleset(cfg.rule_set), 633 | rule_set_ipcidr_match_source: (cfg.rule_set_ipcidr_match_source === '1') || null, 634 | invert: (cfg.invert === '1') || null, 635 | clash_mode: cfg.clash_mode, 636 | outbound: cfg.outbound 637 | }); 638 | }); 639 | 640 | config.route.final = default_outbound; 641 | }; 642 | 643 | /* Rule set */ 644 | if (routing_mode === 'custom') { 645 | uci.foreach(uciconfig, uciruleset, (cfg) => { 646 | if (cfg.enabled !== '1') 647 | return null; 648 | 649 | push(config.route.rule_set, { 650 | type: cfg.type, 651 | tag: cfg.label, 652 | format: cfg.format, 653 | path: cfg.path, 654 | url: cfg.url, 655 | download_detour: cfg.outbound, 656 | update_interval: cfg.update_interval 657 | }); 658 | }); 659 | } 660 | /* Routing rules end */ 661 | 662 | /* Experimental start */ 663 | if (routing_mode === 'custom') { 664 | config.experimental = { 665 | cache_file: { 666 | enabled: true, 667 | path: HP_DIR + '/cache.db', 668 | store_rdrc: (cache_file_store_rdrc === '1') || null, 669 | rdrc_timeout: cache_file_rdrc_timeout 670 | }, 671 | clash_api: { 672 | external_controller: (enable_clash_api === '1') ? external_controller : null, 673 | external_ui: external_ui, 674 | external_ui_download_url: external_ui_download_url, 675 | external_ui_download_detour: external_ui_download_detour, 676 | secret: secret, 677 | default_mode: default_mode 678 | } 679 | }; 680 | } 681 | /* Experimental end */ 682 | 683 | system('mkdir -p ' + RUN_DIR); 684 | writefile(RUN_DIR + '/sing-box-c.json', sprintf('%.J\n', removeBlankAttrs(config))); 685 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/generate_server.uc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ucode 2 | /* 3 | * SPDX-License-Identifier: GPL-2.0-only 4 | * 5 | * Copyright (C) 2023 ImmortalWrt.org 6 | */ 7 | 8 | 'use strict'; 9 | 10 | import { readfile, writefile } from 'fs'; 11 | import { cursor } from 'uci'; 12 | 13 | import { 14 | executeCommand, isEmpty, strToBool, strToInt, 15 | removeBlankAttrs, validateHostname, validation, 16 | HP_DIR, RUN_DIR 17 | } from 'homeproxy'; 18 | 19 | /* UCI config start */ 20 | const uci = cursor(); 21 | 22 | const uciconfig = 'homeproxy'; 23 | uci.load(uciconfig); 24 | 25 | const uciserver = 'server'; 26 | 27 | const config = {}; 28 | 29 | /* Log */ 30 | config.log = { 31 | disabled: false, 32 | level: 'warn', 33 | output: RUN_DIR + '/sing-box-s.log', 34 | timestamp: true 35 | }; 36 | 37 | config.inbounds = []; 38 | 39 | uci.foreach(uciconfig, uciserver, (cfg) => { 40 | if (cfg.enabled !== '1') 41 | return; 42 | 43 | push(config.inbounds, { 44 | type: cfg.type, 45 | tag: 'cfg-' + cfg['.name'] + '-in', 46 | 47 | listen: cfg.address || '::', 48 | listen_port: strToInt(cfg.port), 49 | tcp_fast_open: strToBool(cfg.tcp_fast_open), 50 | tcp_multi_path: strToBool(cfg.tcp_multi_path), 51 | udp_fragment: strToBool(cfg.udp_fragment), 52 | sniff: true, 53 | sniff_override_destination: (cfg.sniff_override === '1'), 54 | domain_strategy: cfg.domain_strategy, 55 | network: cfg.network, 56 | 57 | /* Hysteria */ 58 | up_mbps: strToInt(cfg.hysteria_up_mbps), 59 | down_mbps: strToInt(cfg.hysteria_down_mbps), 60 | obfs: cfg.hysteria_obfs_type ? { 61 | type: cfg.hysteria_obfs_type, 62 | password: cfg.hysteria_obfs_password 63 | } : cfg.hysteria_obfs_password, 64 | recv_window_conn: strToInt(cfg.hysteria_recv_window_conn), 65 | recv_window_client: strToInt(cfg.hysteria_revc_window_client), 66 | max_conn_client: strToInt(cfg.hysteria_max_conn_client), 67 | disable_mtu_discovery: strToBool(cfg.hysteria_disable_mtu_discovery), 68 | ignore_client_bandwidth: strToBool(cfg.hysteria_ignore_client_bandwidth), 69 | masquerade: cfg.hysteria_masquerade, 70 | 71 | /* Shadowsocks */ 72 | method: (cfg.type === 'shadowsocks') ? cfg.shadowsocks_encrypt_method : null, 73 | password: (cfg.type in ['shadowsocks', 'shadowtls']) ? cfg.password : null, 74 | 75 | /* Tuic */ 76 | congestion_control: cfg.tuic_congestion_control, 77 | auth_timeout: cfg.tuic_auth_timeout ? (cfg.tuic_auth_timeout + 's') : null, 78 | zero_rtt_handshake: strToBool(cfg.tuic_enable_zero_rtt), 79 | heartbeat: cfg.tuic_heartbeat ? (cfg.tuic_heartbeat + 's') : null, 80 | 81 | /* HTTP / Hysteria (2) / Socks / Trojan / Tuic / VLESS / VMess */ 82 | users: (cfg.type !== 'shadowsocks') ? [ 83 | { 84 | name: !(cfg.type in ['http', 'socks']) ? 'cfg-' + cfg['.name'] + '-server' : null, 85 | username: cfg.username, 86 | password: cfg.password, 87 | 88 | /* Hysteria */ 89 | auth: (cfg.hysteria_auth_type === 'base64') ? cfg.hysteria_auth_payload : null, 90 | auth_str: (cfg.hysteria_auth_type === 'string') ? cfg.hysteria_auth_payload : null, 91 | 92 | /* Tuic */ 93 | uuid: cfg.uuid, 94 | 95 | /* VLESS / VMess */ 96 | flow: cfg.vless_flow, 97 | alterId: strToInt(cfg.vmess_alterid) 98 | } 99 | ] : null, 100 | 101 | multiplex: (cfg.multiplex === '1') ? { 102 | enabled: true, 103 | padding: (cfg.multiplex_padding === '1'), 104 | brutal: (cfg.multiplex_brutal === '1') ? { 105 | enabled: true, 106 | up_mbps: strToInt(cfg.multiplex_brutal_up), 107 | down_mbps: strToInt(cfg.multiplex_brutal_down) 108 | } : null 109 | } : null, 110 | 111 | tls: (cfg.tls === '1') ? { 112 | enabled: true, 113 | server_name: cfg.tls_sni, 114 | alpn: cfg.tls_alpn, 115 | min_version: cfg.tls_min_version, 116 | max_version: cfg.tls_max_version, 117 | cipher_suites: cfg.tls_cipher_suites, 118 | certificate_path: cfg.tls_cert_path, 119 | key_path: cfg.tls_key_path, 120 | acme: (cfg.tls_acme === '1') ? { 121 | domain: cfg.tls_acme_domains, 122 | data_directory: HP_DIR + '/certs', 123 | default_server_name: cfg.tls_acme_dsn, 124 | email: cfg.tls_acme_email, 125 | provider: cfg.tls_acme_provider, 126 | disable_http_challenge: (cfg.tls_acme_dhc === '1'), 127 | disable_tls_alpn_challenge: (cfg.tls_acme_dtac === '1'), 128 | alternative_http_port: strToInt(cfg.tls_acme_ahp), 129 | alternative_tls_port: strToInt(cfg.tls_acme_atp), 130 | external_account: (cfg.tls_acme_external_account === '1') ? { 131 | key_id: cfg.tls_acme_ea_keyid, 132 | mac_key: cfg.tls_acme_ea_mackey 133 | } : null, 134 | dns01_challenge: (cfg.tls_dns01_challenge === '1') ? { 135 | provider: cfg.tls_dns01_provider, 136 | access_key_id: cfg.tls_dns01_ali_akid, 137 | access_key_secret: cfg.tls_dns01_ali_aksec, 138 | region_id: cfg.tls_dns01_ali_rid, 139 | api_token: cfg.tls_dns01_cf_api_token 140 | } : null 141 | } : null, 142 | reality: (cfg.tls_reality === '1') ? { 143 | enabled: true, 144 | private_key: cfg.tls_reality_private_key, 145 | short_id: cfg.tls_reality_short_id, 146 | max_time_difference: cfg.tls_reality_max_time_difference ? (cfg.max_time_difference + 's') : null, 147 | handshake: { 148 | server: cfg.tls_reality_server_addr, 149 | server_port: cfg.tls_reality_server_port 150 | } 151 | } : null 152 | } : null, 153 | 154 | transport: !isEmpty(cfg.transport) ? { 155 | type: cfg.transport, 156 | host: cfg.http_host || cfg.httpupgrade_host, 157 | path: cfg.http_path || cfg.ws_path, 158 | headers: cfg.ws_host ? { 159 | Host: cfg.ws_host 160 | } : null, 161 | method: cfg.http_method, 162 | max_early_data: strToInt(cfg.websocket_early_data), 163 | early_data_header_name: cfg.websocket_early_data_header, 164 | service_name: cfg.grpc_servicename, 165 | idle_timeout: cfg.http_idle_timeout ? (cfg.http_idle_timeout + 's') : null, 166 | ping_timeout: cfg.http_ping_timeout ? (cfg.http_ping_timeout + 's') : null 167 | } : null 168 | }); 169 | }); 170 | 171 | if (length(config.inbounds) === 0) 172 | exit(1); 173 | 174 | system('mkdir -p ' + RUN_DIR); 175 | writefile(RUN_DIR + '/sing-box-s.json', sprintf('%.J\n', removeBlankAttrs(config))); 176 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/homeproxy.uc: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0-only 3 | * 4 | * Copyright (C) 2023 ImmortalWrt.org 5 | */ 6 | 7 | import { mkstemp } from 'fs'; 8 | import { urldecode, urldecode_params } from 'luci.http'; 9 | 10 | /* Global variables start */ 11 | export const HP_DIR = '/etc/homeproxy'; 12 | export const RUN_DIR = '/var/run/homeproxy'; 13 | /* Global variables end */ 14 | 15 | /* Utilities start */ 16 | /* Kanged from luci-app-commands */ 17 | export function shellQuote(s) { 18 | return `'${replace(s, "'", "'\\''")}'`; 19 | }; 20 | 21 | export function isBinary(str) { 22 | for (let off = 0, byte = ord(str); off < length(str); byte = ord(str, ++off)) 23 | if (byte <= 8 || (byte >= 14 && byte <= 31)) 24 | return true; 25 | 26 | return false; 27 | }; 28 | 29 | export function executeCommand(...args) { 30 | let outfd = mkstemp(); 31 | let errfd = mkstemp(); 32 | 33 | const exitcode = system(`${join(' ', args)} >&${outfd.fileno()} 2>&${errfd.fileno()}`); 34 | 35 | outfd.seek(0); 36 | errfd.seek(0); 37 | 38 | const stdout = outfd.read(1024 * 512) ?? ''; 39 | const stderr = errfd.read(1024 * 512) ?? ''; 40 | 41 | outfd.close(); 42 | errfd.close(); 43 | 44 | const binary = isBinary(stdout); 45 | 46 | return { 47 | command: join(' ', args), 48 | stdout: binary ? null : stdout, 49 | stderr, 50 | exitcode, 51 | binary 52 | }; 53 | }; 54 | 55 | export function calcStringMD5(str) { 56 | if (!str || type(str) !== 'string') 57 | return null; 58 | 59 | const output = executeCommand(`/bin/echo -n ${shellQuote(str)} | /usr/bin/md5sum | /usr/bin/awk '{print $1}'`) || {}; 60 | return trim(output.stdout); 61 | }; 62 | 63 | export function getTime(epoch) { 64 | const local_time = localtime(epoch); 65 | return replace(replace(sprintf( 66 | '%d-%2d-%2d@%2d:%2d:%2d', 67 | local_time.year, 68 | local_time.mon, 69 | local_time.mday, 70 | local_time.hour, 71 | local_time.min, 72 | local_time.sec 73 | ), ' ', '0'), '@', ' '); 74 | 75 | }; 76 | 77 | export function wGET(url) { 78 | if (!url || type(url) !== 'string') 79 | return null; 80 | 81 | const output = executeCommand(`/usr/bin/wget -qO- --user-agent 'Wget/1.21 (HomeProxy, like v2rayN)' --timeout=10 ${shellQuote(url)}`) || {}; 82 | return trim(output.stdout); 83 | }; 84 | /* Utilities end */ 85 | 86 | /* String helper start */ 87 | export function isEmpty(res) { 88 | return !res || res === 'nil' || (type(res) in ['array', 'object'] && length(res) === 0); 89 | }; 90 | 91 | export function strToBool(str) { 92 | return (str === '1') || null; 93 | }; 94 | 95 | export function strToInt(str) { 96 | return !isEmpty(str) ? (int(str) || null) : null; 97 | }; 98 | 99 | export function removeBlankAttrs(res) { 100 | let content; 101 | 102 | if (type(res) === 'object') { 103 | content = {}; 104 | map(keys(res), (k) => { 105 | if (type(res[k]) in ['array', 'object']) 106 | content[k] = removeBlankAttrs(res[k]); 107 | else if (res[k] !== null && res[k] !== '') 108 | content[k] = res[k]; 109 | }); 110 | } else if (type(res) === 'array') { 111 | content = []; 112 | map(res, (k, i) => { 113 | if (type(k) in ['array', 'object']) 114 | push(content, removeBlankAttrs(k)); 115 | else if (k !== null && k !== '') 116 | push(content, k); 117 | }); 118 | } else 119 | return res; 120 | 121 | return content; 122 | }; 123 | 124 | export function validateHostname(hostname) { 125 | return (match(hostname, /^[a-zA-Z0-9_]+$/) != null || 126 | (match(hostname, /^[a-zA-Z0-9_][a-zA-Z0-9_%-.]*[a-zA-Z0-9]$/) && 127 | match(hostname, /[^0-9.]/))); 128 | }; 129 | 130 | export function validation(datatype, data) { 131 | if (!datatype || !data) 132 | return null; 133 | 134 | const ret = system(`/sbin/validate_data ${shellQuote(datatype)} ${shellQuote(data)} 2>/dev/null`); 135 | return (ret === 0); 136 | }; 137 | /* String helper end */ 138 | 139 | /* String parser start */ 140 | export function decodeBase64Str(str) { 141 | if (isEmpty(str)) 142 | return null; 143 | 144 | str = trim(str); 145 | str = replace(str, '_', '/'); 146 | str = replace(str, '-', '+'); 147 | 148 | const padding = length(str) % 4; 149 | if (padding) 150 | str = str + substr('====', padding); 151 | 152 | return b64dec(str); 153 | }; 154 | 155 | export function parseURL(url) { 156 | if (type(url) !== 'string') 157 | return null; 158 | 159 | const services = { 160 | http: '80', 161 | https: '443' 162 | }; 163 | 164 | const objurl = {}; 165 | 166 | objurl.href = url; 167 | 168 | url = replace(url, /#(.+)$/, (_, val) => { 169 | objurl.hash = val; 170 | return ''; 171 | }); 172 | 173 | url = replace(url, /^(\w[A-Za-z0-9\+\-\.]+):/, (_, val) => { 174 | objurl.protocol = val; 175 | return ''; 176 | }); 177 | 178 | url = replace(url, /\?(.+)/, (_, val) => { 179 | objurl.search = val; 180 | objurl.searchParams = urldecode_params(val); 181 | return ''; 182 | }); 183 | 184 | url = replace(url, /^\/\/([^\/]+)/, (_, val) => { 185 | val = replace(val, /^([^@]+)@/, (_, val) => { 186 | objurl.userinfo = val; 187 | return ''; 188 | }); 189 | 190 | val = replace(val, /:(\d+)$/, (_, val) => { 191 | objurl.port = val; 192 | return ''; 193 | }); 194 | 195 | if (validation('ip4addr', val) || 196 | validation('ip6addr', replace(val, /\[|\]/g, '')) || 197 | validation('hostname', val)) 198 | objurl.hostname = val; 199 | 200 | return ''; 201 | }); 202 | 203 | objurl.pathname = url || '/'; 204 | 205 | if (!objurl.protocol || !objurl.hostname) 206 | return null; 207 | 208 | if (objurl.userinfo) { 209 | objurl.userinfo = replace(objurl.userinfo, /:([^:]+)$/, (_, val) => { 210 | objurl.password = val; 211 | return ''; 212 | }); 213 | 214 | if (match(objurl.userinfo, /^[A-Za-z0-9\+\-\_\.]+$/)) { 215 | objurl.username = objurl.userinfo; 216 | delete objurl.userinfo; 217 | } else { 218 | delete objurl.userinfo; 219 | delete objurl.password; 220 | } 221 | }; 222 | 223 | if (!objurl.port) 224 | objurl.port = services[objurl.protocol]; 225 | 226 | objurl.host = objurl.hostname + (objurl.port ? `:${objurl.port}` : ''); 227 | objurl.origin = `${objurl.protocol}://${objurl.host}`; 228 | 229 | return objurl; 230 | }; 231 | /* String parser end */ 232 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/update_crond.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # 4 | # Copyright (C) 2023 ImmortalWrt.org 5 | 6 | SCRIPTS_DIR="/etc/homeproxy/scripts" 7 | 8 | for i in "china_ip4" "china_ip6" "gfw_list" "china_list"; do 9 | "$SCRIPTS_DIR"/update_resources.sh "$i" 10 | done 11 | 12 | "$SCRIPTS_DIR"/update_subscriptions.uc 13 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/update_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # 4 | # Copyright (C) 2022-2023 ImmortalWrt.org 5 | 6 | NAME="homeproxy" 7 | 8 | RESOURCES_DIR="/etc/$NAME/resources" 9 | mkdir -p "$RESOURCES_DIR" 10 | 11 | RUN_DIR="/var/run/$NAME" 12 | LOG_PATH="$RUN_DIR/$NAME.log" 13 | mkdir -p "$RUN_DIR" 14 | 15 | log() { 16 | echo -e "$(date "+%Y-%m-%d %H:%M:%S") $*" >> "$LOG_PATH" 17 | } 18 | 19 | set_lock() { 20 | local act="$1" 21 | local type="$2" 22 | 23 | local lock="$RUN_DIR/update_resources-$type.lock" 24 | if [ "$act" = "set" ]; then 25 | if [ -e "$lock" ]; then 26 | log "[$(to_upper "$type")] A task is already running." 27 | exit 2 28 | else 29 | touch "$lock" 30 | fi 31 | elif [ "$act" = "remove" ]; then 32 | rm -f "$lock" 33 | fi 34 | } 35 | 36 | to_upper() { 37 | echo -e "$1" | tr "[a-z]" "[A-Z]" 38 | } 39 | 40 | check_list_update() { 41 | local listtype="$1" 42 | local listrepo="$2" 43 | local listref="$3" 44 | local listname="$4" 45 | local wget="wget --timeout=10 -q" 46 | 47 | set_lock "set" "$listtype" 48 | 49 | local list_info="$($wget -O- "https://api.github.com/repos/$listrepo/commits?sha=$listref&path=$listname")" 50 | local list_sha="$(echo -e "$list_info" | jsonfilter -e "@[0].sha")" 51 | local list_ver="$(echo -e "$list_info" | jsonfilter -e "@[0].commit.message" | grep -Eo "[0-9-]+" | tr -d '-')" 52 | if [ -z "$list_sha" ] || [ -z "$list_ver" ]; then 53 | log "[$(to_upper "$listtype")] Failed to get the latest version, please retry later." 54 | 55 | set_lock "remove" "$listtype" 56 | return 1 57 | fi 58 | 59 | local local_list_ver="$(cat "$RESOURCES_DIR/$listtype.ver" 2>"/dev/null" || echo "NOT FOUND")" 60 | if [ "$local_list_ver" = "$list_ver" ]; then 61 | log "[$(to_upper "$listtype")] Current version: $list_ver." 62 | log "[$(to_upper "$listtype")] You're already at the latest version." 63 | 64 | set_lock "remove" "$listtype" 65 | return 3 66 | else 67 | log "[$(to_upper "$listtype")] Local version: $local_list_ver, latest version: $list_ver." 68 | fi 69 | 70 | $wget "https://fastly.jsdelivr.net/gh/$listrepo@$list_sha/$listname" -O "$RUN_DIR/$listname" 71 | if [ ! -s "$RUN_DIR/$listname" ]; then 72 | rm -f "$RUN_DIR/$listname" 73 | log "[$(to_upper "$listtype")] Update failed." 74 | 75 | set_lock "remove" "$listtype" 76 | return 1 77 | fi 78 | 79 | mv -f "$RUN_DIR/$listname" "$RESOURCES_DIR/$listtype.${listname##*.}" 80 | echo -e "$list_ver" > "$RESOURCES_DIR/$listtype.ver" 81 | log "[$(to_upper "$listtype")] Successfully updated." 82 | 83 | set_lock "remove" "$listtype" 84 | return 0 85 | } 86 | 87 | case "$1" in 88 | "china_ip4") 89 | check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv4.txt" 90 | ;; 91 | "china_ip6") 92 | check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv6.txt" 93 | ;; 94 | "gfw_list") 95 | check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "gfw.txt" 96 | ;; 97 | "china_list") 98 | check_list_update "$1" "Loyalsoldier/v2ray-rules-dat" "release" "direct-list.txt" && \ 99 | sed -i -e "s/full://g" -e "/:/d" "$RESOURCES_DIR/china_list.txt" 100 | ;; 101 | *) 102 | echo -e "Usage: $0 " 103 | exit 1 104 | ;; 105 | esac 106 | -------------------------------------------------------------------------------- /root/etc/homeproxy/scripts/update_subscriptions.uc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ucode 2 | /* 3 | * SPDX-License-Identifier: GPL-2.0-only 4 | * 5 | * Copyright (C) 2023 ImmortalWrt.org 6 | */ 7 | 8 | 'use strict'; 9 | 10 | import { open } from 'fs'; 11 | import { connect } from 'ubus'; 12 | import { cursor } from 'uci'; 13 | 14 | import { urldecode, urlencode, urldecode_params } from 'luci.http'; 15 | import { init_action } from 'luci.sys'; 16 | 17 | import { 18 | calcStringMD5, wGET, executeCommand, decodeBase64Str, 19 | getTime, isEmpty, parseURL, validation, 20 | HP_DIR, RUN_DIR 21 | } from 'homeproxy'; 22 | 23 | /* UCI config start */ 24 | const uci = cursor(); 25 | 26 | const uciconfig = 'homeproxy'; 27 | uci.load(uciconfig); 28 | 29 | const ucimain = 'config', 30 | ucinode = 'node', 31 | ucisubscription = 'subscription'; 32 | 33 | const allow_insecure = uci.get(uciconfig, ucisubscription, 'allow_insecure') || '0', 34 | filter_mode = uci.get(uciconfig, ucisubscription, 'filter_nodes') || 'disabled', 35 | filter_keywords = uci.get(uciconfig, ucisubscription, 'filter_keywords') || [], 36 | packet_encoding = uci.get(uciconfig, ucisubscription, 'packet_encoding') || 'xudp', 37 | subscription_urls = uci.get(uciconfig, ucisubscription, 'subscription_url') || [], 38 | via_proxy = uci.get(uciconfig, ucisubscription, 'update_via_proxy') || '0'; 39 | 40 | const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainalnd_china'; 41 | let main_node, main_udp_node; 42 | if (routing_mode !== 'custom') { 43 | main_node = uci.get(uciconfig, ucimain, 'main_node') || 'nil'; 44 | main_udp_node = uci.get(uciconfig, ucimain, 'main_udp_node') || 'nil'; 45 | } 46 | /* UCI config end */ 47 | 48 | /* String helper start */ 49 | function filter_check(name) { 50 | if (isEmpty(name) || filter_mode === 'disabled' || isEmpty(filter_keywords)) 51 | return false; 52 | 53 | let ret = false; 54 | for (let i in filter_keywords) { 55 | const patten = regexp(i); 56 | if (match(name, patten)) 57 | ret = true; 58 | } 59 | if (filter_mode === 'whitelist') 60 | ret = !ret; 61 | 62 | return ret; 63 | } 64 | /* String helper end */ 65 | 66 | /* Common var start */ 67 | const node_cache = {}, 68 | node_result = []; 69 | 70 | const ubus = connect(); 71 | const sing_features = ubus.call('luci.homeproxy', 'singbox_get_features', {}) || {}; 72 | /* Common var end */ 73 | 74 | /* Log */ 75 | system(`mkdir -p ${RUN_DIR}`); 76 | function log(...args) { 77 | const logfile = open(`${RUN_DIR}/homeproxy.log`, 'a'); 78 | logfile.write(`${getTime()} [SUBSCRIBE] ${join(' ', args)}\n`); 79 | logfile.close(); 80 | } 81 | 82 | function parse_uri(uri) { 83 | let config, url, params; 84 | 85 | if (type(uri) === 'object') { 86 | if (uri.nodetype === 'sip008') { 87 | /* https://shadowsocks.org/guide/sip008.html */ 88 | config = { 89 | label: uri.remarks, 90 | type: 'shadowsocks', 91 | address: uri.server, 92 | port: uri.server_port, 93 | shadowsocks_encrypt_method: uri.method, 94 | password: uri.password, 95 | shadowsocks_plugin: uri.plugin, 96 | shadowsocks_plugin_opts: uri.plugin_opts 97 | }; 98 | } 99 | } else if (type(uri) === 'string') { 100 | uri = split(trim(uri), '://'); 101 | 102 | switch (uri[0]) { 103 | case 'http': 104 | case 'https': 105 | url = parseURL('http://' + uri[1]); 106 | 107 | config = { 108 | label: url.hash ? urldecode(url.hash) : null, 109 | type: 'http', 110 | address: url.hostname, 111 | port: url.port, 112 | username: url.username ? urldecode(url.username) : null, 113 | password: url.password ? urldecode(url.password) : null, 114 | tls: (uri[0] === 'https') ? '1' : '0' 115 | }; 116 | 117 | break; 118 | case 'hysteria': 119 | /* https://github.com/HyNetwork/hysteria/wiki/URI-Scheme */ 120 | url = parseURL('http://' + uri[1]); 121 | params = url.searchParams; 122 | 123 | if (!sing_features.with_quic || (params.protocol && params.protocol !== 'udp')) { 124 | log(sprintf('Skipping unsupported %s node: %s.', 'hysteria', urldecode(url.hash) || url.hostname)); 125 | if (!sing_features.with_quic) 126 | log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); 127 | 128 | return null; 129 | } 130 | 131 | config = { 132 | label: url.hash ? urldecode(url.hash) : null, 133 | type: 'hysteria', 134 | address: url.hostname, 135 | port: url.port, 136 | hysteria_protocol: params.protocol || 'udp', 137 | hysteria_auth_type: params.auth ? 'string' : null, 138 | hysteria_auth_payload: params.auth, 139 | hysteria_obfs_password: params.obfsParam, 140 | hysteria_down_mbps: params.downmbps, 141 | hysteria_up_mbps: params.upmbps, 142 | tls: '1', 143 | tls_insecure: (params.insecure in ['true', '1']) ? '1' : '0', 144 | tls_sni: params.peer, 145 | tls_alpn: params.alpn 146 | }; 147 | 148 | break; 149 | case 'hysteria2': 150 | case 'hy2': 151 | /* https://v2.hysteria.network/docs/developers/URI-Scheme/ */ 152 | url = parseURL('http://' + uri[1]); 153 | params = url.searchParams; 154 | 155 | if (!sing_features.with_quic) { 156 | log(sprintf('Skipping unsupported %s node: %s.', 'hysteria2', urldecode(url.hash) || url.hostname)); 157 | log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); 158 | return null; 159 | } 160 | 161 | config = { 162 | label: url.hash ? urldecode(url.hash) : null, 163 | type: 'hysteria2', 164 | address: url.hostname, 165 | port: url.port, 166 | password: url.username ? ( 167 | urldecode(url.username + (url.password ? (':' + url.password) : '')) 168 | ) : null, 169 | hysteria_obfs_type: params.obfs, 170 | hysteria_obfs_password: params['obfs-password'], 171 | tls: '1', 172 | tls_insecure: params.insecure ? '1' : '0', 173 | tls_sni: params.sni 174 | }; 175 | 176 | break; 177 | case 'socks': 178 | case 'socks4': 179 | case 'socks4a': 180 | case 'socsk5': 181 | case 'socks5h': 182 | url = parseURL('http://' + uri[1]); 183 | 184 | config = { 185 | label: url.hash ? urldecode(url.hash) : null, 186 | type: 'socks', 187 | address: url.hostname, 188 | port: url.port, 189 | username: url.username ? urldecode(url.username) : null, 190 | password: url.password ? urldecode(url.password) : null, 191 | socks_version: (match(uri[0], /4/)) ? '4' : '5' 192 | }; 193 | 194 | break; 195 | case 'ss': 196 | /* "Lovely" Shadowrocket format */ 197 | const ss_suri = split(uri[1], '#'); 198 | let ss_slabel = ''; 199 | if (length(ss_suri) <= 2) { 200 | if (length(ss_suri) === 2) 201 | ss_slabel = '#' + urlencode(ss_suri[1]); 202 | if (decodeBase64Str(ss_suri[0])) 203 | uri[1] = decodeBase64Str(ss_suri[0]) + ss_slabel; 204 | } 205 | 206 | /* Legacy format is not supported, it should be never appeared in modern subscriptions */ 207 | /* https://github.com/shadowsocks/shadowsocks-org/commit/78ca46cd6859a4e9475953ed34a2d301454f579e */ 208 | 209 | /* SIP002 format https://shadowsocks.org/guide/sip002.html */ 210 | url = parseURL('http://' + uri[1]); 211 | 212 | let ss_userinfo = {}; 213 | if (url.username && url.password) 214 | /* User info encoded with URIComponent */ 215 | ss_userinfo = [url.username, urldecode(url.password)]; 216 | else if (url.username) 217 | /* User info encoded with base64 */ 218 | ss_userinfo = split(decodeBase64Str(urldecode(url.username)), ':'); 219 | 220 | let ss_plugin, ss_plugin_opts; 221 | if (url.search && url.searchParams.plugin) { 222 | const ss_plugin_info = split(url.searchParams.plugin, ';'); 223 | ss_plugin = ss_plugin_info[0]; 224 | if (ss_plugin === 'simple-obfs') 225 | /* Fix non-standard plugin name */ 226 | ss_plugin = 'obfs-local'; 227 | ss_plugin_opts = slice(ss_plugin_info, 1) ? join(';', slice(ss_plugin_info, 1)) : null; 228 | } 229 | 230 | config = { 231 | label: url.hash ? urldecode(url.hash) : null, 232 | type: 'shadowsocks', 233 | address: url.hostname, 234 | port: url.port, 235 | shadowsocks_encrypt_method: ss_userinfo[0], 236 | password: ss_userinfo[1], 237 | shadowsocks_plugin: ss_plugin, 238 | shadowsocks_plugin_opts: ss_plugin_opts 239 | }; 240 | 241 | break; 242 | case 'trojan': 243 | /* https://p4gefau1t.github.io/trojan-go/developer/url/ */ 244 | url = parseURL('http://' + uri[1]); 245 | params = url.searchParams || {}; 246 | 247 | config = { 248 | label: url.hash ? urldecode(url.hash) : null, 249 | type: 'trojan', 250 | address: url.hostname, 251 | port: url.port, 252 | password: urldecode(url.username), 253 | transport: (params.type !== 'tcp') ? params.type : null, 254 | tls: '1', 255 | tls_sni: params.sni 256 | }; 257 | switch(params.type) { 258 | case 'grpc': 259 | config.grpc_servicename = params.serviceName; 260 | break; 261 | case 'ws': 262 | config.ws_host = params.host ? urldecode(params.host) : null; 263 | config.ws_path = params.path ? urldecode(params.path) : null; 264 | if (config.ws_path && match(config.ws_path, /\?ed=/)) { 265 | config.websocket_early_data_header = 'Sec-WebSocket-Protocol'; 266 | config.websocket_early_data = split(config.ws_path, '?ed=')[1]; 267 | config.ws_path = split(config.ws_path, '?ed=')[0]; 268 | } 269 | break; 270 | } 271 | 272 | break; 273 | case 'tuic': 274 | /* https://github.com/daeuniverse/dae/discussions/182 */ 275 | url = parseURL('http://' + uri[1]); 276 | params = url.searchParams || {}; 277 | 278 | if (!sing_features.with_quic) { 279 | log(sprintf('Skipping unsupported %s node: %s.', 'TUIC', urldecode(url.hash) || url.hostname)); 280 | log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); 281 | 282 | return null; 283 | } 284 | 285 | config = { 286 | label: url.hash ? urldecode(url.hash) : null, 287 | type: 'tuic', 288 | address: url.hostname, 289 | port: url.port, 290 | uuid: url.username, 291 | password: url.password ? urldecode(url.password) : null, 292 | tuic_congestion_control: params.congestion_control, 293 | tuic_udp_relay_mode: params.udp_relay_mode, 294 | tls: '1', 295 | tls_sni: params.sni, 296 | tls_alpn: params.alpn ? split(urldecode(params.alpn), ',') : null, 297 | }; 298 | 299 | break; 300 | case 'vless': 301 | /* https://github.com/XTLS/Xray-core/discussions/716 */ 302 | url = parseURL('http://' + uri[1]); 303 | params = url.searchParams; 304 | 305 | /* Unsupported protocol */ 306 | if (params.type === 'kcp') { 307 | log(sprintf('Skipping sunsupported %s node: %s.', 'VLESS', urldecode(url.hash) || url.hostname)); 308 | return null; 309 | } else if (params.type === 'quic' && ((params.quicSecurity && params.quicSecurity !== 'none') || !sing_features.with_quic)) { 310 | log(sprintf('Skipping sunsupported %s node: %s.', 'VLESS', urldecode(url.hash) || url.hostname)); 311 | if (!sing_features.with_quic) 312 | log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); 313 | 314 | return null; 315 | } 316 | 317 | config = { 318 | label: url.hash ? urldecode(url.hash) : null, 319 | type: 'vless', 320 | address: url.hostname, 321 | port: url.port, 322 | uuid: url.username, 323 | transport: (params.type !== 'tcp') ? params.type : null, 324 | tls: (params.security in ['tls', 'xtls', 'reality']) ? '1' : '0', 325 | tls_sni: params.sni, 326 | tls_alpn: params.alpn ? split(urldecode(params.alpn), ',') : null, 327 | tls_reality: (params.security === 'reality') ? '1' : '0', 328 | tls_reality_public_key: params.pbk ? urldecode(params.pbk) : null, 329 | tls_reality_short_id: params.sid, 330 | tls_utls: sing_features.with_utls ? params.fp : null, 331 | vless_flow: (params.security in ['tls', 'reality']) ? params.flow : null 332 | }; 333 | switch(params.type) { 334 | case 'grpc': 335 | config.grpc_servicename = params.serviceName; 336 | break; 337 | case 'http': 338 | case 'tcp': 339 | if (params.type === 'http' || params.headerType === 'http') { 340 | config.http_host = params.host ? split(urldecode(params.host), ',') : null; 341 | config.http_path = params.path ? urldecode(params.path) : null; 342 | } 343 | break; 344 | case 'ws': 345 | config.ws_host = params.host ? urldecode(params.host) : null; 346 | config.ws_path = params.path ? urldecode(params.path) : null; 347 | if (config.ws_path && match(config.ws_path, /\?ed=/)) { 348 | config.websocket_early_data_header = 'Sec-WebSocket-Protocol'; 349 | config.websocket_early_data = split(config.ws_path, '?ed=')[1]; 350 | config.ws_path = split(config.ws_path, '?ed=')[0]; 351 | } 352 | break; 353 | } 354 | 355 | break; 356 | case 'vmess': 357 | /* "Lovely" shadowrocket format */ 358 | if (match(uri, /&/)) { 359 | log(sprintf('Skipping unsupported %s format.', 'VMess')); 360 | return null; 361 | } 362 | 363 | /* https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) */ 364 | try { 365 | uri = json(decodeBase64Str(uri[1])); 366 | } catch(e) { 367 | log(sprintf('Skipping unsupported %s format.', 'VMess')); 368 | return null; 369 | } 370 | 371 | if (uri.v != '2') { 372 | log(sprintf('Skipping unsupported %s format.', 'VMess')); 373 | return null; 374 | /* Unsupported protocol */ 375 | } else if (uri.net === 'kcp') { 376 | log(sprintf('Skipping unsupported %s node: %s.', 'VMess', uri.ps || uri.add)); 377 | return null; 378 | } else if (uri.net === 'quic' && ((uri.type && uri.type !== 'none') || uri.path || !sing_features.with_quic)) { 379 | log(sprintf('Skipping unsupported %s node: %s.', 'VMess', uri.ps || uri.add)); 380 | if (!sing_features.with_quic) 381 | log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); 382 | 383 | return null; 384 | } 385 | /* 386 | * https://www.v2fly.org/config/protocols/vmess.html#vmess-md5-%E8%AE%A4%E8%AF%81%E4%BF%A1%E6%81%AF-%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6 387 | * else if (uri.aid && int(uri.aid) !== 0) { 388 | * log(sprintf('Skipping unsupported %s node: %s.', 'VMess', uri.ps || uri.add)); 389 | * return null; 390 | * } 391 | */ 392 | 393 | config = { 394 | label: uri.ps ? urldecode(uri.ps) : null, 395 | type: 'vmess', 396 | address: uri.add, 397 | port: uri.port, 398 | uuid: uri.id, 399 | vmess_alterid: uri.aid, 400 | vmess_encrypt: uri.scy || 'auto', 401 | vmess_global_padding: '1', 402 | transport: (uri.net !== 'tcp') ? uri.net : null, 403 | tls: (uri.tls === 'tls') ? '1' : '0', 404 | tls_sni: uri.sni || uri.host, 405 | tls_alpn: uri.alpn ? split(uri.alpn, ',') : null 406 | }; 407 | switch (uri.net) { 408 | case 'grpc': 409 | config.grpc_servicename = uri.path; 410 | break; 411 | case 'h2': 412 | case 'tcp': 413 | if (uri.net === 'h2' || uri.type === 'http') { 414 | config.transport = 'http'; 415 | config.http_host = uri.host ? uri.host.split(',') : null; 416 | config.http_path = uri.path; 417 | } 418 | break; 419 | case 'ws': 420 | config.ws_host = uri.host; 421 | config.ws_path = uri.path; 422 | if (config.ws_path && match(config.ws_path, /\?ed=/)) { 423 | config.websocket_early_data_header = 'Sec-WebSocket-Protocol'; 424 | config.websocket_early_data = split(config.ws_path, '?ed=')[1]; 425 | config.ws_path = split(config.ws_path, '?ed=')[0]; 426 | } 427 | break; 428 | } 429 | 430 | break; 431 | } 432 | } 433 | 434 | if (!isEmpty(config)) { 435 | if (config.address) 436 | config.address = replace(config.address, /\[|\]/g, ''); 437 | 438 | if (!validation('host', config.address) || !validation('port', config.port)) { 439 | log(sprintf('Skipping invalid %s node: %s.', config.type, config.label || 'NULL')); 440 | return null; 441 | } else if (!config.label) 442 | config.label = (validation('ip6addr', config.address) ? 443 | `[${config.address}]` : config.address) + ':' + config.port; 444 | } 445 | 446 | return config; 447 | } 448 | 449 | function main() { 450 | if (via_proxy !== '1') { 451 | log('Stopping service...'); 452 | init_action('homeproxy', 'stop'); 453 | } 454 | 455 | for (let url in subscription_urls) { 456 | const groupHash = calcStringMD5(url); 457 | node_cache[groupHash] = {}; 458 | 459 | const res = wGET(url); 460 | if (!res) { 461 | log(sprintf('Failed to fetch resources from %s.', url)); 462 | continue; 463 | } 464 | 465 | push(node_result, []); 466 | const subindex = length(node_result) - 1; 467 | 468 | let nodes; 469 | try { 470 | nodes = json(res).servers || json(res); 471 | 472 | /* Shadowsocks SIP008 format */ 473 | if (nodes[0].server && nodes[0].method) 474 | map(nodes, (_, i) => nodes[i].nodetype = 'sip008'); 475 | } catch(e) { 476 | nodes = decodeBase64Str(res); 477 | nodes = nodes ? split(trim(replace(nodes, / /g, '_')), '\n') : {}; 478 | } 479 | 480 | let count = 0; 481 | for (let node in nodes) { 482 | let config; 483 | if (!isEmpty(node)) 484 | config = parse_uri(node); 485 | if (isEmpty(config)) 486 | continue; 487 | 488 | const label = config.label; 489 | config.label = null; 490 | const confHash = calcStringMD5(sprintf('%J', config)), 491 | nameHash = calcStringMD5(label); 492 | config.label = label; 493 | 494 | if (filter_check(config.label)) 495 | log(sprintf('Skipping blacklist node: %s.', config.label)); 496 | else if (node_cache[groupHash][confHash] || node_cache[groupHash][nameHash]) 497 | log(sprintf('Skipping duplicate node: %s.', config.label)); 498 | else { 499 | if (config.tls === '1' && allow_insecure === '1') 500 | config.tls_insecure = '1'; 501 | if (config.type in ['vless', 'vmess']) 502 | config.packet_encoding = packet_encoding; 503 | 504 | config.grouphash = groupHash; 505 | push(node_result[subindex], config); 506 | node_cache[groupHash][confHash] = config; 507 | node_cache[groupHash][nameHash] = config; 508 | 509 | count++; 510 | } 511 | } 512 | 513 | log(sprintf('Successfully fetched %s nodes of total %s from %s.', count, length(nodes), url)); 514 | } 515 | 516 | if (isEmpty(node_result)) { 517 | log('Failed to update subscriptions: no valid node found.'); 518 | 519 | if (via_proxy !== '1') { 520 | log('Starting service...'); 521 | init_action('homeproxy', 'start'); 522 | } 523 | 524 | return false; 525 | } 526 | 527 | let added = 0, removed = 0; 528 | uci.foreach(uciconfig, ucinode, (cfg) => { 529 | /* Nodes created by the user */ 530 | if (!cfg.grouphash) 531 | return null; 532 | 533 | /* Empty object - failed to fetch nodes */ 534 | if (length(node_cache[cfg.grouphash]) === 0) 535 | return null; 536 | 537 | if (!node_cache[cfg.grouphash] || !node_cache[cfg.grouphash][cfg['.name']]) { 538 | uci.delete(uciconfig, cfg['.name']); 539 | removed++; 540 | 541 | log(sprintf('Removing node: %s.', cfg.label || cfg['name'])); 542 | } else { 543 | map(keys(node_cache[cfg.grouphash][cfg['.name']]), (v) => { 544 | uci.set(uciconfig, cfg['.name'], v, node_cache[cfg.grouphash][cfg['.name']][v]); 545 | }); 546 | node_cache[cfg.grouphash][cfg['.name']].isExisting = true; 547 | } 548 | }); 549 | for (let nodes in node_result) 550 | map(nodes, (node) => { 551 | if (node.isExisting) 552 | return null; 553 | 554 | const nameHash = calcStringMD5(node.label); 555 | uci.set(uciconfig, nameHash, 'node'); 556 | map(keys(node), (v) => uci.set(uciconfig, nameHash, v, node[v])); 557 | 558 | added++; 559 | log(sprintf('Adding node: %s.', node.label)); 560 | }); 561 | uci.commit(uciconfig); 562 | 563 | let need_restart = (via_proxy !== '1'); 564 | if (!isEmpty(main_node)) { 565 | const first_server = uci.get_first(uciconfig, ucinode); 566 | if (first_server) { 567 | if (!uci.get(uciconfig, main_node)) { 568 | uci.set(uciconfig, ucimain, 'main_node', first_server); 569 | uci.commit(uciconfig); 570 | need_restart = true; 571 | 572 | log('Main node is gone, switching to the first node.'); 573 | } 574 | 575 | if (!isEmpty(main_udp_node) && main_udp_node !== 'same') { 576 | if (!uci.get(uciconfig, main_udp_node)) { 577 | uci.set(uciconfig, ucimain, 'main_udp_node', first_server); 578 | uci.commit(uciconfig); 579 | need_restart = true; 580 | 581 | log('Main UDP node is gone, switching to the first node.'); 582 | } 583 | } 584 | } else { 585 | uci.set(uciconfig, ucimain, 'main_node', 'nil'); 586 | uci.set(uciconfig, ucimain, 'main_udp_node', 'nil'); 587 | uci.commit(uciconfig); 588 | need_restart = true; 589 | 590 | log('No available node, disable tproxy.'); 591 | } 592 | } 593 | 594 | if (need_restart) { 595 | log('Restarting service...'); 596 | init_action('homeproxy', 'stop'); 597 | init_action('homeproxy', 'start'); 598 | } 599 | 600 | log(sprintf('%s nodes added, %s removed.', added, removed)); 601 | log('Successfully updated subscriptions.'); 602 | } 603 | 604 | if (!isEmpty(subscription_urls)) 605 | try { 606 | call(main); 607 | } catch(e) { 608 | log('[FATAL ERROR] An error occurred during updating subscriptions:'); 609 | log(sprintf('%s: %s', e.type, e.message)); 610 | log(e.stacktrace[0].context); 611 | 612 | log('Restarting service...'); 613 | init_action('homeproxy', 'stop'); 614 | init_action('homeproxy', 'start'); 615 | } 616 | -------------------------------------------------------------------------------- /root/etc/init.d/homeproxy: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # 4 | # Copyright (C) 2022-2023 ImmortalWrt.org 5 | 6 | USE_PROCD=1 7 | 8 | START=99 9 | STOP=10 10 | 11 | CONF="homeproxy" 12 | PROG="/usr/bin/sing-box" 13 | 14 | HP_DIR="/etc/homeproxy" 15 | RUN_DIR="/var/run/homeproxy" 16 | LOG_PATH="$RUN_DIR/homeproxy.log" 17 | DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-homeproxy.d" 18 | 19 | log() { 20 | echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH" 21 | } 22 | 23 | start_service() { 24 | config_load "$CONF" 25 | 26 | local routing_mode proxy_mode 27 | config_get routing_mode "config" "routing_mode" "bypass_mainland_china" 28 | config_get proxy_mode "config" "proxy_mode" "redirect_tproxy" 29 | 30 | local outbound_node 31 | if [ "$routing_mode" != "custom" ]; then 32 | config_get outbound_node "config" "main_node" "nil" 33 | else 34 | config_get outbound_node "routing" "default_outbound" "nil" 35 | fi 36 | 37 | local server_enabled 38 | config_get_bool server_enabled "server" "enabled" "0" 39 | 40 | if [ "$outbound_node" = "nil" ] && [ "$server_enabled" = "0" ]; then 41 | return 1 42 | fi 43 | 44 | mkdir -p "$RUN_DIR" 45 | 46 | if [ "$outbound_node" != "nil" ]; then 47 | # Generate/Validate client config 48 | ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH" 49 | 50 | if [ ! -e "$RUN_DIR/sing-box-c.json" ]; then 51 | log "Error: failed to generate client configuration." 52 | return 1 53 | elif ! "$PROG" check --config "$RUN_DIR/sing-box-c.json" 2>>"$LOG_PATH"; then 54 | log "Error: wrong client configuration detected." 55 | return 1 56 | fi 57 | 58 | # Auto update 59 | local auto_update auto_update_time 60 | config_get_bool auto_update "subscription" "auto_update" "0" 61 | if [ "$auto_update" = "1" ]; then 62 | config_get auto_update_time "subscription" "auto_update_time" "2" 63 | echo -e "0 $auto_update_time * * * $HP_DIR/scripts/update_crond.sh" >> "/etc/crontabs/root" 64 | /etc/init.d/cron restart 65 | fi 66 | 67 | # DNSMasq rules 68 | local ipv6_support 69 | config_get_bool ipv6_support "config" "ipv6_support" "0" 70 | local dns_port china_dns_server china_dns_port 71 | config_get dns_port "infra" "dns_port" "5333" 72 | mkdir -p "$DNSMASQ_DIR" 73 | echo -e "conf-dir=$DNSMASQ_DIR" > "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" 74 | case "$routing_mode" in 75 | "gfwlist") 76 | [ "$ipv6_support" -eq "0" ] || local gfw_nftset_v6=",6#inet#fw4#homeproxy_gfw_list_v6" 77 | sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port\nnftset=\/\1\\/4#inet#fw4#homeproxy_gfw_list_v4$gfw_nftset_v6/g" \ 78 | "$HP_DIR/resources/gfw_list.txt" > "$DNSMASQ_DIR/gfw_list.conf" 79 | ;; 80 | "bypass_mainland_china") 81 | config_get china_dns_server "config" "china_dns_server" 82 | config_get china_dns_port "infra" "china_dns_port" "5334" 83 | 84 | if [ -e "/usr/bin/chinadns-ng" ] && [ -n "$china_dns_server" ]; then 85 | cat <<-EOF >> "$DNSMASQ_DIR/redirect-dns.conf" 86 | no-poll 87 | no-resolv 88 | server=127.0.0.1#$china_dns_port 89 | EOF 90 | else 91 | china_dns_server="" 92 | sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port/g" \ 93 | "$HP_DIR/resources/gfw_list.txt" > "$DNSMASQ_DIR/gfw_list.conf" 94 | fi 95 | ;; 96 | "proxy_mainland_china") 97 | sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port/g" \ 98 | "$HP_DIR/resources/china_list.txt" > "$DNSMASQ_DIR/china_list.conf" 99 | ;; 100 | "custom"|"global") 101 | cat <<-EOF >> "$DNSMASQ_DIR/redirect-dns.conf" 102 | no-poll 103 | no-resolv 104 | server=127.0.0.1#$dns_port 105 | EOF 106 | ;; 107 | esac 108 | 109 | if [ "$routing_mode" != "custom" ] && [ -s "$HP_DIR/resources/proxy_list.txt" ]; then 110 | [ "$ipv6_support" -eq "0" ] || local wan_nftset_v6=",6#inet#fw4#homeproxy_wan_proxy_addr_v6" 111 | 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" \ 112 | "$HP_DIR/resources/proxy_list.txt" > "$DNSMASQ_DIR/proxy_list.conf" 113 | fi 114 | /etc/init.d/dnsmasq restart >"/dev/null" 2>&1 115 | 116 | # Setup routing table 117 | local table_mark 118 | config_get table_mark "infra" "table_mark" "100" 119 | case "$proxy_mode" in 120 | "redirect_tproxy") 121 | local outbound_udp_node 122 | config_get outbound_udp_node "config" "main_udp_node" "nil" 123 | if [ "$outbound_udp_node" != "nil" ] || [ "$routing_mode" = "custom" ]; then 124 | local tproxy_mark 125 | config_get tproxy_mark "infra" "tproxy_mark" "101" 126 | 127 | ip rule add fwmark "$tproxy_mark" table "$table_mark" 128 | ip route add local 0.0.0.0/0 dev lo table "$table_mark" 129 | 130 | if [ "$ipv6_support" -eq "1" ]; then 131 | ip -6 rule add fwmark "$tproxy_mark" table "$table_mark" 132 | ip -6 route add local ::/0 dev lo table "$table_mark" 133 | fi 134 | fi 135 | ;; 136 | "redirect_tun"|"tun") 137 | local tun_name tun_mark 138 | config_get tun_name "infra" "tun_name" "singtun0" 139 | config_get tun_mark "infra" "tun_mark" "102" 140 | 141 | ip tuntap add mode tun user root name "$tun_name" 142 | sleep 1s 143 | ip link set "$tun_name" up 144 | 145 | ip route replace default dev "$tun_name" table "$table_mark" 146 | ip rule add fwmark "$tun_mark" lookup "$table_mark" 147 | 148 | ip -6 route replace default dev "$tun_name" table "$table_mark" 149 | ip -6 rule add fwmark "$tun_mark" lookup "$table_mark" 150 | ;; 151 | esac 152 | 153 | # sing-box (client) 154 | procd_open_instance "sing-box-c" 155 | 156 | procd_set_param command "$PROG" 157 | procd_append_param command run --config "$RUN_DIR/sing-box-c.json" 158 | 159 | if [ -x "/sbin/ujail" ] && [ "$routing_mode" != "custom" ] && ! grep -Eq '"type": "(wireguard|tun)"' "$RUN_DIR/sing-box-c.json"; then 160 | procd_add_jail "sing-box-c" log procfs 161 | procd_add_jail_mount "$RUN_DIR/sing-box-c.json" 162 | procd_add_jail_mount_rw "$RUN_DIR/sing-box-c.log" 163 | procd_add_jail_mount "$HP_DIR/certs/" 164 | procd_add_jail_mount "/etc/ssl/" 165 | procd_add_jail_mount "/etc/localtime" 166 | procd_add_jail_mount "/etc/TZ" 167 | procd_set_param capabilities "/etc/capabilities/homeproxy.json" 168 | procd_set_param no_new_privs 1 169 | procd_set_param user sing-box 170 | procd_set_param group sing-box 171 | fi 172 | 173 | procd_set_param limits core="unlimited" 174 | procd_set_param limits nofile="1000000 1000000" 175 | procd_set_param stderr 1 176 | procd_set_param respawn 177 | 178 | procd_close_instance 179 | 180 | # chinadns-ng 181 | if [ -n "$china_dns_server" ]; then 182 | local wandns="$(ifstatus wan | jsonfilter -e '@["dns-server"][0]' || echo "119.29.29.29")" 183 | china_dns_server="${china_dns_server/wan/$wandns}" 184 | china_dns_server="${china_dns_server// /,}" 185 | 186 | for i in $(seq 1 "$(grep -c "processor" "/proc/cpuinfo")"); do 187 | procd_open_instance "chinadns-ng-$i" 188 | 189 | procd_set_param command "/usr/bin/chinadns-ng" 190 | procd_append_param command --bind-port "$china_dns_port" 191 | procd_append_param command --china-dns "$china_dns_server" 192 | procd_append_param command --trust-dns "127.0.0.1#$dns_port" 193 | procd_append_param command --ipset-name4 "inet@fw4@homeproxy_mainland_addr_v4" 194 | procd_append_param command --ipset-name6 "inet@fw4@homeproxy_mainland_addr_v6" 195 | procd_append_param command --chnlist-file "$HP_DIR/resources/china_list.txt" 196 | procd_append_param command --gfwlist-file "$HP_DIR/resources/gfw_list.txt" 197 | [ "$ipv6_support" -eq "1" ] || procd_append_param command --no-ipv6=tC 198 | procd_append_param command --reuse-port 199 | 200 | if chinadns-ng --version | grep -q "target:"; then 201 | procd_append_param command --cache 10000 202 | procd_append_param command --cache-stale 3600 203 | procd_append_param command --verdict-cache 10000 204 | fi 205 | 206 | if [ -x "/sbin/ujail" ]; then 207 | procd_add_jail "chinadns-ng" log 208 | procd_add_jail_mount "$HP_DIR/resources/china_list.txt" 209 | procd_add_jail_mount "$HP_DIR/resources/gfw_list.txt" 210 | procd_set_param capabilities "/etc/capabilities/homeproxy.json" 211 | procd_set_param no_new_privs 1 212 | procd_set_param user sing-box 213 | procd_set_param group sing-box 214 | fi 215 | 216 | procd_set_param limits core="unlimited" 217 | procd_set_param limits nofile="1000000 1000000" 218 | procd_set_param stderr 1 219 | procd_set_param respawn 220 | 221 | procd_close_instance 222 | done 223 | fi 224 | fi 225 | 226 | if [ "$server_enabled" = "1" ]; then 227 | # Generate/Validate server config 228 | ucode -S "$HP_DIR/scripts/generate_server.uc" 2>>"$LOG_PATH" 229 | 230 | if [ ! -e "$RUN_DIR/sing-box-s.json" ]; then 231 | log "Error: failed to generate server configuration." 232 | return 1 233 | elif ! "$PROG" check --config "$RUN_DIR/sing-box-s.json" 2>>"$LOG_PATH"; then 234 | log "Error: wrong server configuration detected." 235 | return 1 236 | fi 237 | 238 | # sing-box (server) 239 | procd_open_instance "sing-box-s" 240 | 241 | procd_set_param command "$PROG" 242 | procd_append_param command run --config "$RUN_DIR/sing-box-s.json" 243 | 244 | if [ -x "/sbin/ujail" ]; then 245 | procd_add_jail "sing-box-s" log procfs 246 | procd_add_jail_mount "$RUN_DIR/sing-box-s.json" 247 | procd_add_jail_mount_rw "$RUN_DIR/sing-box-s.log" 248 | procd_add_jail_mount "$HP_DIR/certs/" 249 | procd_add_jail_mount "/etc/localtime" 250 | procd_add_jail_mount "/etc/TZ" 251 | procd_set_param capabilities "/etc/capabilities/homeproxy.json" 252 | procd_set_param no_new_privs 1 253 | procd_set_param user sing-box 254 | procd_set_param group sing-box 255 | fi 256 | 257 | procd_set_param limits core="unlimited" 258 | procd_set_param limits nofile="1000000 1000000" 259 | procd_set_param stderr 1 260 | procd_set_param respawn 261 | 262 | procd_close_instance 263 | fi 264 | 265 | # log-cleaner 266 | procd_open_instance "log-cleaner" 267 | procd_set_param command "$HP_DIR/scripts/clean_log.sh" 268 | procd_set_param respawn 269 | procd_close_instance 270 | 271 | # Prepare ruleset directory for custom routing mode 272 | if [ "$routing_mode" = "custom" ]; then 273 | [ -d "$HP_DIR/ruleset" ] || mkdir -p "$HP_DIR/ruleset" 274 | fi 275 | 276 | # Update permissions for ujail 277 | if [ "$outbound_node" != "nil" ]; then 278 | echo > "$RUN_DIR/sing-box-c.log" 279 | chown sing-box:sing-box "$RUN_DIR/sing-box-c.log" 280 | chown sing-box:sing-box "$RUN_DIR/sing-box-c.json" 281 | chmod 0644 "$HP_DIR/resources/gfw_list.txt" 282 | fi 283 | if [ "$server_enabled" = "1" ]; then 284 | echo > "$RUN_DIR/sing-box-s.log" 285 | chown sing-box:sing-box "$RUN_DIR/sing-box-s.log" 286 | chown sing-box:sing-box "$RUN_DIR/sing-box-s.json" 287 | fi 288 | 289 | # Setup firewall 290 | utpl -S "$HP_DIR/scripts/firewall_pre.ut" > "$RUN_DIR/fw4_pre.nft" 291 | [ "$outbound_node" = "nil" ] || utpl -S "$HP_DIR/scripts/firewall_post.ut" > "$RUN_DIR/fw4_post.nft" 292 | fw4 reload >"/dev/null" 2>&1 293 | 294 | log "$(sing-box version | awk 'NR==1{print $1,$3}') started." 295 | } 296 | 297 | stop_service() { 298 | sed -i "/$CONF/d" "/etc/crontabs/root" 2>"/dev/null" 299 | /etc/init.d/cron restart >"/dev/null" 2>&1 300 | 301 | # Setup firewall 302 | # Load config 303 | config_load "$CONF" 304 | local table_mark tproxy_mark tun_mark tun_name 305 | config_get table_mark "infra" "table_mark" "100" 306 | config_get tproxy_mark "infra" "tproxy_mark" "101" 307 | config_get tun_mark "infra" "tun_mark" "102" 308 | config_get tun_name "infra" "tun_name" "singtun0" 309 | 310 | # Tproxy 311 | ip rule del fwmark "$tproxy_mark" table "$table_mark" 2>"/dev/null" 312 | ip route del local 0.0.0.0/0 dev lo table "$table_mark" 2>"/dev/null" 313 | ip -6 rule del fwmark "$tproxy_mark" table "$table_mark" 2>"/dev/null" 314 | ip -6 route del local ::/0 dev lo table "$table_mark" 2>"/dev/null" 315 | 316 | # TUN 317 | ip route del default dev "$tun_name" table "$table_mark" 2>"/dev/null" 318 | ip rule del fwmark "$tun_mark" table "$table_mark" 2>"/dev/null" 319 | 320 | ip -6 route del default dev "$tun_name" table "$table_mark" 2>"/dev/null" 321 | ip -6 rule del fwmark "$tun_mark" table "$table_mark" 2>"/dev/null" 322 | 323 | # Nftables rules 324 | for i in "homeproxy_dstnat_redir" "homeproxy_output_redir" \ 325 | "homeproxy_redirect" "homeproxy_redirect_proxy" \ 326 | "homeproxy_redirect_proxy_port" "homeproxy_redirect_lanac" \ 327 | "homeproxy_mangle_prerouting" "homeproxy_mangle_output" \ 328 | "homeproxy_mangle_tproxy" "homeproxy_mangle_tproxy_port" \ 329 | "homeproxy_mangle_tproxy_lanac" "homeproxy_mangle_mark" \ 330 | "homeproxy_mangle_tun" "homeproxy_mangle_tun_mark"; do 331 | nft flush chain inet fw4 "$i" 332 | nft delete chain inet fw4 "$i" 333 | done 2>"/dev/null" 334 | for i in "homeproxy_local_addr_v4" "homeproxy_local_addr_v6" \ 335 | "homeproxy_gfw_list_v4" "homeproxy_gfw_list_v6" \ 336 | "homeproxy_mainland_addr_v4" "homeproxy_mainland_addr_v6" \ 337 | "homeproxy_wan_proxy_addr_v4" "homeproxy_wan_proxy_addr_v6" \ 338 | "homeproxy_wan_direct_addr_v4" "homeproxy_wan_direct_addr_v6" \ 339 | "homeproxy_routing_port"; do 340 | nft flush set inet fw4 "$i" 341 | nft delete set inet fw4 "$i" 342 | done 2>"/dev/null" 343 | echo > "$RUN_DIR/fw4_pre.nft" 2>"/dev/null" 344 | echo > "$RUN_DIR/fw4_post.nft" 2>"/dev/null" 345 | fw4 reload >"/dev/null" 2>&1 346 | 347 | # Remove DNS hijack 348 | rm -rf "$DNSMASQ_DIR/../dnsmasq-homeproxy.conf" "$DNSMASQ_DIR" 349 | /etc/init.d/dnsmasq restart >"/dev/null" 2>&1 350 | 351 | rm -f "$RUN_DIR/sing-box-c.json" "$RUN_DIR/sing-box-c.log" \ 352 | "$RUN_DIR/sing-box-s.json" "$RUN_DIR/sing-box-s.log" 353 | 354 | log "Service stopped." 355 | } 356 | 357 | service_stopped() { 358 | # Load config 359 | config_load "$CONF" 360 | local tun_name 361 | config_get tun_name "infra" "tun_name" "singtun0" 362 | 363 | # TUN 364 | ip link set "$tun_name" down 2>"/dev/null" 365 | ip tuntap del mode tun name "$tun_name" 2>"/dev/null" 366 | } 367 | 368 | reload_service() { 369 | log "Reloading service..." 370 | 371 | stop 372 | start 373 | } 374 | 375 | service_triggers() { 376 | procd_add_reload_trigger "$CONF" 377 | procd_add_interface_trigger "interface.*.up" wan /etc/init.d/$CONF reload 378 | } 379 | -------------------------------------------------------------------------------- /root/etc/uci-defaults/luci-homeproxy: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | uci -q batch <<-EOF >"/dev/null" 4 | delete firewall.homeproxy_pre 5 | set firewall.homeproxy_pre=include 6 | set firewall.homeproxy_pre.type=nftables 7 | set firewall.homeproxy_pre.path="/var/run/homeproxy/fw4_pre.nft" 8 | set firewall.homeproxy_pre.position="table-pre" 9 | 10 | delete firewall.homeproxy_post 11 | set firewall.homeproxy_post=include 12 | set firewall.homeproxy_post.type=nftables 13 | set firewall.homeproxy_post.path="/var/run/homeproxy/fw4_post.nft" 14 | set firewall.homeproxy_post.position="table-post" 15 | commit firewall 16 | EOF 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /root/etc/uci-defaults/luci-homeproxy-migration: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | china_dns_server="$(uci -q get "homeproxy.config.china_dns_server")" 4 | if [ "$china_dns_server" = "wan_114" ]; then 5 | uci -q delete "homeproxy.config.china_dns_server" 6 | uci -q add_list "homeproxy.config.china_dns_server"="wan" 7 | uci -q add_list "homeproxy.config.china_dns_server"="114.114.114.114" 8 | elif echo "$china_dns_server" | grep -q ","; then 9 | uci -q delete "homeproxy.config.china_dns_server" 10 | for dns in ${china_dns_server//,/ }; do 11 | uci -q add_list "homeproxy.config.china_dns_server"="$dns" 12 | done 13 | fi 14 | [ -z "$(uci -q changes "homeproxy")" ] || uci -q commit "homeproxy" 15 | 16 | exit 0 17 | -------------------------------------------------------------------------------- /root/usr/share/luci/menu.d/luci-app-homeproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/homeproxy": { 3 | "title": "HomeProxy", 4 | "order": 10, 5 | "action": { 6 | "type": "firstchild" 7 | }, 8 | "depends": { 9 | "acl": [ "luci-app-homeproxy" ], 10 | "uci": { "homeproxy": true } 11 | } 12 | }, 13 | "admin/services/homeproxy/client": { 14 | "title": "Client Settings", 15 | "order": 10, 16 | "action": { 17 | "type": "view", 18 | "path": "homeproxy/client" 19 | } 20 | }, 21 | "admin/services/homeproxy/node": { 22 | "title": "Node Settings", 23 | "order": 15, 24 | "action": { 25 | "type": "view", 26 | "path": "homeproxy/node" 27 | } 28 | }, 29 | "admin/services/homeproxy/server": { 30 | "title": "Server Settings", 31 | "order": 20, 32 | "action": { 33 | "type": "view", 34 | "path": "homeproxy/server" 35 | } 36 | }, 37 | "admin/services/homeproxy/status": { 38 | "title": "Service Status", 39 | "order": 30, 40 | "action": { 41 | "type": "view", 42 | "path": "homeproxy/status" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /root/usr/share/rpcd/acl.d/luci-app-homeproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-homeproxy": { 3 | "description": "Grant access to homeproxy configuration", 4 | "read": { 5 | "file": { 6 | "/etc/homeproxy/scripts/update_subscriptions.uc": [ "exec" ], 7 | "/var/run/homeproxy/homeproxy.log": [ "read" ], 8 | "/var/run/homeproxy/sing-box-c.log": [ "read" ], 9 | "/var/run/homeproxy/sing-box-s.log": [ "read" ] 10 | }, 11 | "ubus": { 12 | "service": [ "list" ], 13 | "luci.homeproxy": [ "*" ] 14 | }, 15 | "uci": [ "homeproxy" ] 16 | }, 17 | "write": { 18 | "file": { 19 | "/tmp/homeproxy_certificate.tmp": [ "write" ] 20 | }, 21 | "uci": [ "homeproxy" ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /root/usr/share/rpcd/ucode/luci.homeproxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ucode 2 | /* 3 | * SPDX-License-Identifier: GPL-2.0-only 4 | * 5 | * Copyright (C) 2023 ImmortalWrt.org 6 | */ 7 | 8 | 'use strict'; 9 | 10 | import { access, error, lstat, mkstemp, popen, readfile, writefile } from 'fs'; 11 | 12 | /* Kanged from ucode/luci */ 13 | function shellquote(s) { 14 | return `'${replace(s, "'", "'\\''")}'`; 15 | } 16 | 17 | function hasKernelModule(kmod) { 18 | return (system(sprintf('[ -e "/lib/modules/$(uname -r)"/%s ]', shellquote(kmod))) === 0); 19 | } 20 | 21 | const HP_DIR = '/etc/homeproxy'; 22 | const RUN_DIR = '/var/run/homeproxy'; 23 | 24 | const methods = { 25 | acllist_read: { 26 | args: { type: 'type' }, 27 | call: function(req) { 28 | if (index(['direct_list', 'proxy_list'], req.args?.type) === -1) 29 | return { content: null, error: 'illegal type' }; 30 | 31 | const filecontent = readfile(`${HP_DIR}/resources/${req.args?.type}.txt`); 32 | return { content: filecontent }; 33 | } 34 | }, 35 | acllist_write: { 36 | args: { type: 'type', content: 'content' }, 37 | call: function(req) { 38 | if (index(['direct_list', 'proxy_list'], req.args?.type) === -1) 39 | return { result: false, error: 'illegal type' }; 40 | 41 | const file = `${HP_DIR}/resources/${req.args?.type}.txt`; 42 | let content = req.args?.content; 43 | 44 | /* Sanitize content */ 45 | if (content) { 46 | content = trim(content); 47 | content = replace(content, /\r\n?/g, '\n'); 48 | if (!match(content, /\n$/)) 49 | content += '\n'; 50 | } 51 | 52 | system(`mkdir -p ${HP_DIR}/resources`); 53 | writefile(file, content); 54 | 55 | return { result: true }; 56 | } 57 | }, 58 | 59 | certificate_write: { 60 | args: { filename: 'filename' }, 61 | call: function(req) { 62 | const writeCertificate = function(filename, priv) { 63 | const tmpcert = '/tmp/homeproxy_certificate.tmp'; 64 | const filestat = lstat(tmpcert); 65 | 66 | if (!filestat || filestat.type !== 'file' || filestat.size <= 0) { 67 | system(`rm -f ${tmpcert}`); 68 | return { result: false, error: 'empty certificate file' }; 69 | } 70 | 71 | let filecontent = readfile(tmpcert); 72 | if (is_binary(filecontent)) { 73 | system(`rm -f ${tmpcert}`); 74 | return { result: false, error: 'illegal file type: binary' }; 75 | } 76 | 77 | /* Kanged from luci-proto-openconnect */ 78 | const beg = priv ? /^-----BEGIN (RSA|EC) PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/, 79 | end = priv ? /^-----END (RSA|EC) PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/, 80 | lines = split(trim(filecontent), /[\r\n]/); 81 | let start = false, i; 82 | 83 | for (i = 0; i < length(lines); i++) { 84 | if (match(lines[i], beg)) 85 | start = true; 86 | else if (start && !b64dec(lines[i]) && length(lines[i]) !== 64) 87 | break; 88 | } 89 | 90 | if (!start || i < length(lines) - 1 || !match(lines[i], end)) { 91 | system(`rm -f ${tmpcert}`); 92 | return { result: false, error: 'this does not look like a correct PEM file' }; 93 | } 94 | 95 | /* Sanitize certificate */ 96 | filecontent = trim(filecontent); 97 | filecontent = replace(filecontent, /\r\n?/g, '\n'); 98 | if (!match(filecontent, /\n$/)) 99 | filecontent += '\n'; 100 | 101 | system(`mkdir -p ${HP_DIR}/certs`); 102 | writefile(`${HP_DIR}/certs/${filename}.pem`, filecontent); 103 | system(`rm -f ${tmpcert}`); 104 | 105 | return { result: true }; 106 | }; 107 | 108 | const filename = req.args?.filename; 109 | switch (filename) { 110 | case 'client_ca': 111 | case 'server_publickey': 112 | return writeCertificate(filename, false); 113 | break; 114 | case 'server_privatekey': 115 | return writeCertificate(filename, true); 116 | break; 117 | default: 118 | return { result: false, error: 'illegal cerificate filename' }; 119 | break; 120 | } 121 | } 122 | }, 123 | 124 | connection_check: { 125 | args: { site: 'site' }, 126 | call: function(req) { 127 | let url; 128 | switch(req.args?.site) { 129 | case 'baidu': 130 | url = 'https://www.baidu.com'; 131 | break; 132 | case 'google': 133 | url = 'https://www.google.com'; 134 | break; 135 | default: 136 | return { result: false, error: 'illegal site' }; 137 | break; 138 | } 139 | 140 | return { result: (system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100) === 0) }; 141 | } 142 | }, 143 | 144 | log_clean: { 145 | args: { type: 'type' }, 146 | call: function(req) { 147 | if (!(req.args?.type in ['homeproxy', 'sing-box-c', 'sing-box-s'])) 148 | return { result: false, error: 'illegal type' }; 149 | 150 | const filestat = lstat(`${RUN_DIR}/${req.args?.type}.log`); 151 | if (filestat) 152 | writefile(`${RUN_DIR}/${req.args?.type}.log`, ''); 153 | return { result: true }; 154 | } 155 | }, 156 | 157 | singbox_get_features: { 158 | call: function() { 159 | let features = {}; 160 | 161 | const fd = popen('/usr/bin/sing-box version'); 162 | if (fd) { 163 | for (let line = fd.read('line'); length(line); line = fd.read('line')) { 164 | let tags = match(trim(line), /Tags: (.*)/); 165 | if (!tags) 166 | continue; 167 | 168 | for (let i in split(tags[1], ',')) 169 | features[i] = true; 170 | } 171 | 172 | fd.close(); 173 | } 174 | 175 | features.hp_has_chinadns_ng = access('/usr/bin/chinadns-ng'); 176 | if (features.hp_has_chinadns_ng) 177 | features.hp_has_chinadns_ng_v2 = (system('/usr/bin/chinadns-ng --version | grep -q "target:"') === 0); 178 | features.hp_has_ip_full = access('/usr/libexec/ip-full'); 179 | features.hp_has_tcp_brutal = hasKernelModule('brutal.ko'); 180 | features.hp_has_tproxy = hasKernelModule('nft_tproxy.ko') || access('/etc/modules.d/nft-tproxy'); 181 | features.hp_has_tun = hasKernelModule('tun.ko') || access('/etc/modules.d/30-tun'); 182 | 183 | return features; 184 | } 185 | }, 186 | 187 | resources_get_version: { 188 | args: { type: 'type' }, 189 | call: function(req) { 190 | const version = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`)); 191 | return { version: version, error: error() }; 192 | } 193 | }, 194 | resources_update: { 195 | args: { type: 'type' }, 196 | call: function(req) { 197 | if (req.args?.type) { 198 | const type = shellquote(req.args?.type); 199 | const exit_code = system(`${HP_DIR}/scripts/update_resources.sh ${type}`); 200 | return { status: exit_code }; 201 | } else 202 | return { status: 255, error: 'illegal type' }; 203 | } 204 | } 205 | }; 206 | 207 | return { 'luci.homeproxy': methods }; 208 | --------------------------------------------------------------------------------